Форум: "Потрепаться";
Текущий архив: 2004.10.03;
Скачать: [xml.tar.bz2];
ВнизПотокобезопасность или как написать потокобезопасный код ? Найти похожие ветки
← →
kaZaNoVa (2004-09-09 09:20) [0]Тут на форуме часто обсуждают, насколько потокобезопасна та или иная функция ...
- а что это значит ?
и как написать потокобезопасную функцию ?
// у меня был случай - создавал 50 потоков методом CreateThread и была глобальная переменная num, использовалась так:
var num:integer;
.....
ThreadProcedure;
begin
.....
If num < 400 then inc(num);
.....
end;
так иногда num в конце получалась 412 -440 !!!
- как такое возможно ???
← →
Digitman © (2004-09-09 09:38) [1]пусть с переменной num работают всего два трэда, пусть в некий момент времени num = 399
смотрим что получается :
1-й поток получил квант времени и успел выполнить только проверку
If num < 400 then // истина !
здесь произошло переключение контекста
теперь 2-й поток получил квант времени и тоже успел выполнить только проверку
If num < 400 then // истина !
здесь вновь произошло переключение контекста, вновь 1-й поток получил квант времени и выполнил
inc(num); // 400
здесь вновь произошло переключение контекста, вновь 2-й поток получил квант времени и выполнил
inc(num); // 401
чуешь чем дело пахнет ?
← →
Григорьев Антон © (2004-09-09 09:38) [2]Потокобезопасная функция - это та, которая ведёт себя корректно при одновременном вызове из рачных нитей. Корректность должна проявляться в синхронизированном доступе к совместно используемым ресурсам. Вот, например, пусть в вашем случае Num=399. Одна из нитей выполнила сравнение Num<400, выяснила, что условие истинно, и тут система передала квант времени другой нити. Та тоже выполнила сравнение, сочла условие истинным, увеличила Num на единицу. Квант времени вернулся к первой нити. Она продолжила работу. Проверка условия в ней уже выполнена, поэтому она сразу переходит к увеличению Num. В результате получаем, что Num=401. А когда нитей будет много, то, может быть, и больше.
Чтобы избежать этого, надо сделать так, чтобы между сравнением Num и его увеличением ни одна другая нить, использующая Num, не могла получить квант времени. Делается это с помощью критических секций или мьютексов. Ищите в MSDN"е информацию по ключемым словам EnterCriticalSection и CreateMutex.
← →
Григорьев Антон © (2004-09-09 09:39) [3]> Digitman © (09.09.04 09:38) [1]
Эх, чуть-чуть я не успел! :))
← →
Layner © (2004-09-09 09:40) [4]Ну так делай NUM для каждого потока свой, т.е. массив, и будет не более 400.
← →
kaZaNoVa (2004-09-09 09:41) [5]да, точно, как-то интересно выполняется ...
- надо тогда что-то типа "синхронизации" делать ?
- а как ?
// я когда первый раз с этим столкнулся - был в шоке :))
- придумал sleep(random(50)) -чтобы разнести потоки по времени ..
но всё-равно глючит иногда ...
← →
KSergey © (2004-09-09 09:42) [6]> [5] kaZaNoVa (09.09.04 09:41)
> // я когда первый раз с этим столкнулся - был в шоке :))
> - придумал sleep(random(50))
Может тогда проще в одном было все делать? И проблем никаких ;)
А так 50, да и те спят... ;)
← →
kaZaNoVa (2004-09-09 09:43) [7][4] Layner © (09.09.04 09:40)
нет, надо именно глобальную ..
- это была программа для проверки списков прокси, где каждый прокси был в масссиве, и каждая нить брала для проверки proxy[num] прокси ....
и она глючила ...
← →
kaZaNoVa (2004-09-09 09:44) [8][6] KSergey © (09.09.04 09:42)
нет я делал от 5 до 200
лучше всего работало 50
← →
Digitman © (2004-09-09 09:46) [9]
> kaZaNoVa (09.09.04 09:41) [5]
> - надо тогда что-то типа "синхронизации" делать ?
> - а как ?
для того в системе и существуют объекты синхронизации : критические секции, мьютексы, семафоры, ивенты, сообщения и пр. и пр.
← →
kaZaNoVa (2004-09-09 09:47) [10][2] Григорьев Антон © (09.09.04 09:38)
> EnterCriticalSection и CreateMutex.
а их использование не замедлит выполнение нитей ?
← →
kaZaNoVa (2004-09-09 09:50) [11]- у кого-нить есть пример использования объектов синхронизации ?
и какой из них работает быстрее ?
← →
Григорьев Антон © (2004-09-09 09:51) [12]
> kaZaNoVa (09.09.04 09:47) [10]
> [2] Григорьев Антон © (09.09.04 09:38)
>
> > EnterCriticalSection и CreateMutex.
>
> а их использование не замедлит выполнение нитей ?
Конечно, замедлит :)) Любой вызов функции - это как минимум несколько тактов процессора на call и ret. А ведь функция должна ещё и какие-то действия внутри себя выполнить.
← →
Digitman © (2004-09-09 10:04) [13]
> kaZaNoVa (09.09.04 09:50) [11]
> - у кого-нить есть пример использования объектов синхронизации
> ?
пример использования
мьютекса, семафора, ивента
\Delphi\Demos\Ipcdemos\ipcthrd.pas
крит.секции
\Delphi\Demos\Midas\Pooler\pooler.exe
> и какой из них работает быстрее ?
быстрей работают крит.секции, но использованы они могут быть только для синхронизации трэдов в контексте одного и того же процесса
мьютексы же, семафоры и ивенты могут использоваться для синхронизации потоков, принадлежащих разным процессам
← →
Игорь Шевченко © (2004-09-09 10:12) [14]
> - у кого-нить есть пример использования объектов синхронизации
> ?
http://www.schevchenko.net.ru/SRC/SuperMarket_50.zip
← →
Alex Konshin © (2004-09-09 10:36) [15]Ну, прямо скажем, для threadsafe инкремента глобальной переменной достаточно InterlockedIncrement. А так читай про критические секции, events и функции ожидания событий (WaitFor..., MsgWaitFor...).
И еще: для создания потоков лучше использовать BeginThread, а не createThread.
← →
iZEN © (2004-09-09 10:42) [16]Модуль: SyncObjs.
Класс: TCriticalSection.
Демонстрация(не помню точно): статья на сайте "Королевство Дельфи".
← →
kaZaNoVa (2004-09-09 22:59) [17]ок, всем спасибо, сделал так:
procedure ThreadFunc;
var
cs:_RTL_CRITICAL_SECTION;
begin
InitializeCriticalSection(cs);
EnterCriticalSection(cs);
If num < 400 then inc(num);
LeaveCriticalSection(cs);
DeleteCriticalSection(cs);
end;
- правильно ?
← →
Alex Konshin © (2004-09-09 23:06) [18]Неправильно.
Критическая секция должна быть одна для всех потоков.
Совет: посмотри в Win API функции Interlocked*
← →
kaZaNoVa (2004-09-09 23:25) [19]да . .. я заметил .. тот мой код (17) глючит ....
← →
iZEN © (2004-09-09 23:41) [20]Нечто подобное:
interface
uses SyncObjs,...;
...
var
CS: TCriticalSection;
...
implementation
...
initialization
CS := TCriticalSection.Create();
finalization
CS.Free();
en.
Из любого модуля с нитями пользуем внутри Execute() нити:
CS.Enter(); ////на память пишу имя метода входа в крит.секцию
...//защищённый потокобезопасные вычисления при условии, если и другие нити пользуются входом в CS
CS.Leave();//на память пишу имя метода выхода из крит.секции
← →
kaZaNoVa (2004-09-09 23:49) [21][20] iZEN © (09.09.04 23:41)
это, как я понял - для класса TThread ...
а если Апи-шные нити юзать (BeginThread) - то там также ?
или надо по другому ?
← →
iZEN © (2004-09-10 00:06) [22]to kaZaNoVa (09.09.04 23:49) [21].
Borland категорически не рекомендует смешивать вызовы Win API и своей библиотеки RTL по части управления нитями.
Так что либо юзать ТОЛЬКО Borland RTL (TThread, TCriticalSection и VCL), либо переходить ПОЛНОСТЬЮ на WinAPI.
Иначе столкнётесь с трудноразрешимыми проблемами (решения есть, но трудны).
Так, предупредил, типа.
Delphi Object Pascal довольно мощный язык. Мне, например, применять потокобезопасный код удобнее через RTL. На самом деле RTL - это ещё обёртки над примитивными объектами синхронизации Windows такими как: мьютексы, семафоры, крит.секции и т.д., сделанные в общей идеологии защиты памяти и данных Delphi.
Либо рисковать и усиленно думать над каждой деталью, либо использовать готовые наработки. Думайте.
← →
kaZaNoVa (2004-09-10 00:25) [23][22] iZEN © (10.09.04 00:06)
спасибо, постараюсь перейти на RTL ...
- просто у меня была недавно задача написать ультра-маленькую программу - чисто на апи на делфе - TCP- сервер многопоточный+ несколько разных функций, так удалось написать 8 кб :)))) (Delphi 7 + подмена/сжатие по самое нехочу ::)))))))))
← →
iZEN © (2004-09-10 01:44) [24]/**kaZaNoVa (10.09.04 00:25) [23]
- просто у меня была недавно задача написать ультра-маленькую программу - чисто на апи на делфе - TCP- сервер многопоточный+ несколько разных функций, так удалось написать 8 кб :)))) (Delphi 7 + подмена/сжатие по самое нехочу ::)))))))))
*/
Обязательно должен быть пулинг коннектов.
Вкратце принцип. Например пятьдесят работающих/ждущих нитей и при очередном коннекте передаёте управление одной из ожидающих нитей. Обработав запрос клиента, нить не завершается, а приостанавливается, вычищаются данные сессии клиента, а сама нить отклыдывается в пул до следующего раза. Сервер, в этом случае, может держать пятьдесят активных коннектов без пересоздания нитей, что очень важно по времени реакции и памяти. Если кто-то ещё будет подключаться - то он будет вставать в очередь на обслуживание пока не освободиться хотя бы одна нить.
Без пулинга долго такой сервер не проживёт.
← →
Alex Konshin © (2004-09-10 03:09) [25]Borland категорически не рекомендует смешивать вызовы Win API и своей библиотеки RTL по части управления нитями.
Так что либо юзать ТОЛЬКО Borland RTL (TThread, TCriticalSection и VCL), либо переходить ПОЛНОСТЬЮ на WinAPI.
Иначе столкнётесь с трудноразрешимыми проблемами (решения есть, но трудны).
Это какие такие могут быть проблемы при использовании TRTLCriticalSection (вроде так в Delphi обзывается структура из Win API)?
← →
Fay © (2004-09-10 03:15) [26]2 iZEN © (10.09.04 00:06) [22]
Не стоит серьёзно воспринимать такие предупреждения. Это, видимо, особенности перевода. 8)
Да и зачем кому-либо, кто дружит с API, всякие TThread-ы?
← →
iZEN © (2004-09-10 03:56) [27]to Fay © (10.09.04 03:15) [26].
Есть пара примеров по Delphi3, где смешение кода при работе с нитями вызывает проблемы. Но, наверное, сейчас в D5/D6/D7 это неактуально.
← →
kaZaNoVa (2004-09-10 11:47) [28][26] Fay © (10.09.04 03:15)
> Да и зачем кому-либо, кто дружит с API, всякие TThread-ы?
Полностю согласен !
-меня вообще раздражает сама идея использования класса Tthread - надо её описывать, мутить всякие execute и т.д. ...
иначе же CreateThread - описал процедуру - и запустил !
так просто, и изящно ;)
и никаких имхо проблем ;))
// сейчас перешёл на BeginThread
[24] iZEN © (10.09.04 01:44)
я делал по новой нити на каждый новый коннект - работало как часы, ни одного сбоя ..
// правда больше чем 20 подключений одновременно не было ..
а самое прияное, что если что и всё-таки заглючит, то все проблемы решаются переподключением - та нить завершается, а новая с "чистыми" переменными снова работает на ура !
и ненадо никого ждать ...
> без пересоздания нитей, что очень важно по времени реакции
> и памяти.
- может и была задержка - но я её не заметил ...
работало хорошо
← →
iZEN © (2004-09-10 18:17) [29]to kaZaNoVa (10.09.04 11:47) [28].
Интересно, сколько коннектов в секунду выдерживал Ваш сервер?
(Я имею ввиду свойства отработки (потраченное время, нагрузка по памяти и процессору) относительно короткого запроса в одном соединении).
Вот смотрите, на каждое соединение у Вас всегда создаётся новая нить, она же впоследствии разрушается после завершения соединения. Если, к примеру, на Ваш сервер будет массовый наплыв желающих и количество соединений превысит разумные возможности Windows обслужить их, то Ваш сервер вскоре накроется медным тазом, унося с собой себя и систему. DoS-атаки ориентированы в первую очередь именно на такие сервера, которые не могут контролировать количество допустимых активных соединений, поэтому им ничего не стоит перегрузить систему и/или сделать её недоступной продолжительное время.
Пулинг и гибкая политика управления количеством и приоритетом нитей, обслуживающих соединения, способствует линейной масштабируемости системы и, прежде всего, защиты операционной системы от DoS-атак (сервер может долго необслуживать, если заняты все "линии" пулинга при DoS-атаке, но система будет цела и невредима, контроль сохраняется!).
Если использовать только функции Windows API, то, естественно, выигрыш в быстродействии будет процентов 30% относительно реализации на основе RTL. Но тогда переносит такие приложения в Kylix/Linux, к примеру, будет уже невозможно.
← →
kaZaNoVa (2004-09-10 19:57) [30]to [29] iZEN © (10.09.04 18:17)
спасибо за профессиональный анализ ;)
>Интересно, сколько коннектов в секунду выдерживал Ваш сервер?
не замерял ... - попробую как-нить нагрузить его "сверх меры" но думаю, Вы правы, он зависнет ...
>на Ваш сервер будет массовый наплыв желающих и количество соединений
>превысит разумные возможности Windows обслужить их, то Ваш сервер
>вскоре накроется медным тазом, унося с собой себя и систему
я, признаться, никогда не думал, что мой сервак будет так популярен или что на него будет дос - атака .....
- имхо это удел сверхпопулярных продуктов ....
- Но, как вариант - в таком случае предлогаю отслеживать количество запущенных потоков/открытых сокетов и при привышения определённой меры/подозрении на дос -атаку (много конектов с одного IP) либо сразу закрывать сокет, без создания нити, либо передавать такие сокеты на очередь отдельному потоку, который не торопясь, будет посылать всем мессаги типа "SERVER TOO BIZY" или что-нить в этом роде, без обработки запроса и закрывать сокет.
- тогда я думаю будет надёжно и устойчиво ;)))))
// хотя, я не сталкивался с необходимостью обработки очень большого количества запросов, так что скорее всего могу ошибаться ....
>Пулинг и гибкая политика управления количеством и приоритетом нитей
да .. хорошая идея, стоит модумать над реализацией .......
- я использовал блокирующие сокеты, а с ними имхо проще всего 1 поток-1 сокет .. ;)
>Если использовать только функции Windows API, то, естественно, выигрыш в
>быстродействии будет процентов 30% относительно реализации на основе
>RTL. Но тогда переносит такие приложения в Kylix/Linux, к примеру, будет
>уже невозможно.
- мне использование апи-функций нравится потому что:
1) быстродействие
2) надёжность
3) отсутствие сообщений о ошибках (в большинстве случаев)
4) не увеличивают размер программы ;))))))))))
про критические секции - сделал так, работает
implementation
Var
...
cs:_RTL_CRITICAL_SECTION;
........
procedure ThreadFunc;
begin
EnterCriticalSection(cs);
If num < 450 then
begin
sleep(random(100));
inc(num);
end;
LeaveCriticalSection(cs);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
InitializeCriticalSection(cs);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
DeleteCriticalSection(cs);
end;
← →
iZEN © (2004-09-10 20:20) [31]to kaZaNoVa (10.09.04 19:57) [30].
Не занимайте всё время критическую секцию [sleep(random(100))]!!! Нужно дать работать и другим нитям.
Критическая секция служит для удержания непротиворечивого состояния данных от действий других нитей только на время изменения этих данных текущей нитью, и это время должно быть МИНИМАЛЬНЫМ. То есть, если где-то по пути между Enter() и Leave() будет продолжительный цикл или пауза, которая никак не влияют на изменчивость данных, то остальные нити будут просто ждать завершения этого цикла или паузы всё это время, ВХОЛОСТУЮ простаивая до выхода текущей нити из критической секции по Leave().
← →
kaZaNoVa (2004-09-10 20:29) [32][31] iZEN © (10.09.04 20:20)
- пример со sleep -был для "прикола" - так я тестил на потокобезопасность, работает великолепно ! хотя медленно ;)
конечно же потом я sleep убрал .. :)
;)))))))))))))))))))
Страницы: 1 вся ветка
Форум: "Потрепаться";
Текущий архив: 2004.10.03;
Скачать: [xml.tar.bz2];
Память: 0.55 MB
Время: 0.06 c