| | | | |
Работа с потоками в Delphi: так ли страшен чёрт, как его малюют? | Полный текст материала
Другие публикации автора: Юрий Балыкин
Цитата или краткий комментарий: «... Данная статья предназначена для начинающих программистов, которые никогда не работали с потоками, и хотели бы узнать основы работы с ними. ...» |
Важно:- Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
- Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
- При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
- Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.
Добавить свое мнение.
| | Содержит полезные и(или) интересные сведения | [1] | 20 | 83.3% | | | | Ничего особенно нового и интересного | [2] | 4 | 16.7% | | | | Написано неверно (обязательно укажите почему) | [3] | 0 | 0% | | Всего проголосовали: 24 | | | Все понятно, материал читается легко | [1] | 23 | 100% | | | | Есть неясности в изложении | [2] | 0 | 0% | | | | Непонятно написано, трудно читается | [3] | 0 | 0% | | Всего проголосовали: 23 |
[TThread] [Потоки (нити) Threads]
Отслеживать это обсуждение
Всего сообщений: 4818-11-2009 14:34
18-11-2009 14:19
02-07-2009 02:42В качестве аналогии можно автотранспорт привести:
Пусть у нас есть две дороги, по которым движутся автомобили (это наши потоки). В одном месте они пересекаются. Перекресток это и есть наш объект. Пока машин мало, они могут проезжать через перекресток друг другу не мешая. Но когда их станет больше, обязательно найдутся две, которые захотят проехать через перекресток одновременно и столкнутся. В этот момент может произойти порча данных и прочие неприятности. Чтобы столкновения не происходили, на перекрестках ставят средства синхронизации - семафоры или регулировщиков. |
|
01-07-2009 11:45>>> Поток, сгенерировавший событие, приостанавливается
Вам лучше задать этот вопрос не в обсуждении, а как отдельный вопрос. Потому что вот так, вкратце, задача не понятна. Понятно, что вызывающий поток остановится, но чего вы хотите достичь, используя "таймер" в потоке мне совершенно непонятно. |
|
01-07-2009 08:44 Под передачей потоку управления ничего путевого я не подразумевал. Не разобрался просто, в том числе и с терминологией. Но с вашей помощью дела идут в гору. |
|
01-07-2009 02:08А передать управление потоку можно, в том числе, вызвав метод того самого объекта. Что вы понимаете под "передать управление потоку"?
Если Вы из основного потока вызовите метод объекта созданного в потоке, то этот метод будет выполняться в основном потоке, что может привести (а может не привести) к ошибке. Надо стремиться свести к минимуму взаимодействие потоков, т.е. всё, что создано внутри потока в нём же и используется и уничтожается, а всё остальное для него не существует. Что делать, когда всё-таки приходится организовывать взаимодействие, довольно подробно описано. |
|
01-07-2009 02:04
01-07-2009 00:43 "...метод не может быть привязан ни к одной нити". Вот. Поэтому я и говорю о недопонимании с моей стороны. Я-то как понимаю: если в потоке у меня создан некий объект, то все функционирование этого объекта идет только тогда, когда этому потоку передается управление. А передать управление потоку можно, в том числе, вызвав метод того самого объекта. Ошибся, видимо. А сколько таких ошибок еще будет? Поэтому и прошу статью :) |
|
01-07-2009 00:35Я написал "...событие, к которому привязана процедура..." Конечно же, привязан метод.
А какая разница? Принципиальное отличие метода от процедуры только одно: у метода существует неявный параметр Self. И метод, как и процедура, не может быть привязан ни к одной нити. |
|
01-07-2009 00:16 К момему предыдущему сообщению. Я написал "...событие, к которому привязана процедура..." Конечно же, привязан метод. Извините, что не в тему, но может быть кто-нибудь напишет статью на тему навроде: "организация системы сбора данных с реализацией опроса порта и вычислений в разных потоках"? Судя по всему, это - классическая задача, а приходится перелопачивать тонны макулатуры (извиняюсь за точное выражение) в поисках ее реализации. |
|
30-06-2009 23:53Вот что осталось (среди прочего) не понятым: если в потоке в процедуре Execute генерируется некое событие (к примеру, TNotifyEvent), к которому привязана процедура из другого потока
Понятия привязки процедуры к нити не существует в принципе, процедура - это просто кусок кода, который может выполняться любой нитью, а при необходимости - несколькими нитями параллельно. Поэтому прямой вызов события из другой нити приведёт к тому, что эта самая нить просто выполнит код обработчика этого события.
А набегание ошибки таймера неизбежно всегда, таймер в Windows - очень неточный инструмент. |
|
30-06-2009 08:24 Вот что осталось (среди прочего) не понятым: если в потоке в процедуре Execute генерируется некое событие (к примеру, TNotifyEvent), к которому привязана процедура из другого потока, то что происходит при генерации события? Поток, сгенерировавший событие, приостанавливается, управление передается потоку-обработчику события и т.д. и т.п.? А если в одном потоке у меня таймер, в другом: опрос порта и вычисления по тику таймера, тогда как быть?... "Набегание" ошибки таймера неизбежно? |
|
06-05-2009 02:10Спасибо большое.
Про "умножение сущностей" написано зря, с таких статей начинать изучение в 100 раз эффективнее, нежели просто куря справки/хелпы/мануалы. |
|
05-02-2009 02:37Мне тоже статья понравилось, спасибо! :)) |
|
04-02-2009 05:04Большое спасибо, побольше бы таких простых и понятных статей |
|
17-09-2008 13:03Есть идеи завести паблик-свойство отражающее процент расчета
Идея правильная.
а также добавить событие когда это значение именяется.
А вот это уже ни к чему. Пользователю совершенно необязательно знать о том, что в данную милисекунду выполнено ровно 33% работы, а вызов события (да ещё и через Synchronize) может (иногда существенно) замедлить сам расчёт. Я же считаю, что расчёт приоритетен, а проценты это так, для особо нетерпеливых, поэтому обновляю прогресс по таймеру, например раз в секунду (можно и реже, зависит от задачи). Это кстати упрощает и расчёт "оставшегося времени", которое часто показывается вместе с прогрессом, и изменение "паблик-свойства", потому что не надо вызывать метод, достаточно изменить поле, а делать это можно сколь угодно часто не боясь затормозить алгоритм. |
|
17-09-2008 04:38Пока писал пришло в голову использовать сообщения ))) Гоните дурные мысли. Можно и сообщения, но зачем городить огород? Если в событии TNotifyEvent стандартно указывается Sender (чтобы узнать у кого конеретно оно произошло), кроме того после набора кода Thread1. легко догадаться что нужно сделать. А при использовании сообщений Windows надо это еще както доходчиво задокументировать, разжевать будущим пользователям. |
|
17-09-2008 03:56В дополнение:
Вопрос "правильной" практической реализации, который давно меня мучает. Скажем есть сложный математический расчет с кучей циклов, разветвлений и т.д. Он, понятное дело, оформлен отдельным классом в отдельном модуле. Как сделать для такого варианта что-то наподобие прогрессбара? Есть идеи завести паблик-свойство отражающее процент расчета, а также добавить событие когда это значение именяется. Из программы запустить расчет в отдельном потоке, а в событии изменения процента расчета использовать синхронизацию. Пока писал пришло в голову использовать сообщения ))) |
|
15-09-2008 22:27
29-07-2008 17:25Статья для новичков, безусловно, понятная. А осмелев и поэкспериментировав с десятком разных примеров, можно уже переходить и к более продвинутому уровню.
Я как раз нахожусь в этом положении. И разбирая один свой эксперимент с потоком, наткнулся на некоторое затруднение. Вопрос всем, и автору, может, Вы даже упомянете похожие случаи в своей будущей статье. Заранее спасибо.
Итак, имеются 2 модуля - главный и потока.
В главном модуле:
WordApp:=TWordApplication.Create(NIL);
WordApp.ConnectKind:=ckNewInstance;
WordDoc:=TWordDocument.Create(NIL);
...
загрузка в Word документа в режиме редактирования
...
вызов потока MyThread
...
ПОПЫТКА ПРОДОЛЖИТЬ РАБОТУ С ЗАГРУЖЕННЫМ ДОКУМЕНТОМ
В модуле потока метод Execute:
CoInitializeEx(NIL,COINIT_APARTMENTTHREADED);
WordApp:=TWordApplication.Create(NIL);
WordApp.ConnectKind:=ckNewInstance;
WordDoc:=TWordDocument.Create(NIL);
...
загрузка в Word документа в режиме редактирования, но без фактического редактирования - просто получение статистики
...
WordApp.Disconnect;
WordDoc.Free;
WordApp.Free;
CoUninitialize;
И вот. При попытке продолжить работу с документом - ошибка - не дает работать с регионами (Range), внесенными в массив до вызова потока. В чем может быть причина? Поток, он вообще загружает/выгружает не один документ, а все, находящиеся в нужной папке. Я не настаиваю, но, вроде, ошибка сохраняется, даже если в потоке загрузить другой документ Word
|
|
26-07-2008 11:16Прочитал и сразу захотелось написать что нибудь многопоточное! :)
IMHO очень мало информации по обмену данными между потоком и приложением.
>>>... например работа с сокетами, COM-портом ...
подразумевает получение потоком определенного обьема данных и просто посылкой сообщения тут не обойтись.
>>>Общение потока с программой может быть реализовано огромным количеством способов - выбирайте любой, в основном я пользуюсь сообщениями. (C) Python, обсуждение "Создаем дружественный интерфейс"
Т.к. статья "Hello, World!" думаю в ней будет уместно упомянуть и о других безопасных способах передачи данных. Хотя бы для "правильной ориентации" читателя в нужном направлении. |
|
24-07-2008 06:19
22-07-2008 12:04Да, Queue работает так же, как и Synchronize, только не ждёт завершения работы метода. Т.е. ставит метод в очередь и сразу же выходит. |
|
22-07-2008 11:11сообщение от автора материала А в некоторых, помимо него, есть ещё и метод Queue.
Если не ошибаюсь, Queue не останавливает выполнение потока?
В Delphi7 точно этого нет. В других версиях, увы, работать не приходилось. |
|
22-07-2008 06:42>>> рекомендация (совет) по теме Synchronize(). Его можно использовать из дополнительного потока не создавая лишних объектов TThread и лишних статических методов
Не во всякой Delphi есть статический Synchronize.
А в некоторых, помимо него, есть ещё и метод Queue. |
|
22-07-2008 06:22Полезная статья. Сам с потоками работал мало, теперь же вижу явные ошибки в своих многотпотоковых программах. |
|
19-07-2008 00:55сообщение от автора материала 2 Дмитрий
За замечания спасибо.
Что в статье особенного, нового? - абсолютно ничего. Эта статья для начинающих, только основы и всё.
По поводу остальных замечаний: я думаю написать продолжение, где всё это более подробно разобрать на примере конкретного приложения. Ведь кроме Ваших замечаний много неясностей остаётся - как объекты создавать, как с ними работать, как данными между потоками обмениваться и т.д.
у Рихтера классно разжеван материал, касаемый реализации многопоточности в Windows. Советую его почитать.
Вот как раз после его прочтения у меня и появилась мысль статью написать :) |
|
18-07-2008 16:322 Дмитрий:
Все-таки Вы неоправданно строги к автору. Объем информации для HelloWorld вполне достаточен (с анализ флага Terminated согл.), а лаконичность и понятность - одно из главных достоинств. То, что надо, для начала работы. А в обсуждении много чего есть в развитие темы.
За замечания - респект, очень ценно. |
|
18-07-2008 15:29Что в статье особенного, нового? Подобного материала в интернете весьма много, изложенного не менее подробно. Начинающий программист наверно оценит (часть ДО критических секций изложена неплохо), но тут в целом изложен самый минимум (вроде материал есть, но для практических дел его явно маловато).
Мои замечания и рекомендации:
- Реально работающие параллельно потоки могут быть только на машинах с двумя и более процессорами.
про многоядерные процессоры тоже забывать не стоит. В них реальная параллельность.
- В однопоточном приложении Sleep используется редко, а вот в потоках его использовать очень удобно. Пример - бесконечный цикл, пока не выполнится какое-нибудь условие. Если не вставить туда Sleep мы будем просто нагружать систему бесполезной работой.
удобнее использовать объекты ядра Windows - Events, а Sleep() заменить на WaitFor[SingleObject]. Тем самым появляется дополнительное преимущество - возможность в любой момент прервать ожидание путем вызова SetEvent() из основного потока.
- не раскрыта тема завершения работы потока по анализу флага Terminated, а это - один из главных пунктов.
- рекомендация (совет) по теме Synchronize(). Его можно использовать из дополнительного потока не создавая лишних объектов TThread и лишних статических методов. Такой прием весьма удобен:
TThread.Synchronize(nil, Form1.Show);
TThread.Synchronize(nil, Form1.Hide);
TThread.Synchronize(nil, Form1.Close);
TThread.Synchronize(nil, Form1.Free);
- Самый интересный способ, на мой взгляд — критические секции. Работают они следующим образом: внутри критической секции может работать только один поток, другие ждут его завершения.
завершение кого? потока? или блокировки крит. секции? Рекомендую более качественно изучить тему критических секций и исправить формулировку.
- есть еще события (TEvent), а так же объекты системы, такие как мьютексы (Mutex), семафоры (Semaphore), но они больше подходят для взаимодействия между приложениями.
Событий TEvent нет. В Windows они по-другому называются. Другое дело, в модуле SyncObjs есть класс-обертка TEvent. Кстати Эвенты используются чаще для синхронизации именно между потоками одного процесса (работу с СОМ-портом, например, по-другому не построишь). Тема объектов синхронизации подробно раскрыта в MSDN.
- у Рихтера классно разжеван материал, касаемый реализации многопоточности в Windows. Советую его почитать. Очень много нового можно для себя почерпнуть. |
|
18-07-2008 02:302Ins, ваше замечание о приоритетах некорректно. Оно следует из чтения документации, но в жизни все не так.
Посмотрите на dtf.ru статью о многозадачности Windows.
Для поддержки быстрого реагирования на проиходящие события в потоке, нужно оставить приоритет потоков равным, но самостоятельно предусмотреть частые регулярные "слипы", отдающие часть процессорного времени ядру системы. Тогда система будет чаще переключаться между потоками, чтобы "раздать" всем времени поровну.
Мое замечание корректно :) Но Вы его истолковали неверно. Я говорил о потоках, которые должны выполнить определенные не занимающие много времени действия сразу же, как условия пробуждения потока будут выполнены. А в этом случае, мне нужно, чтобы приоритет потока был не равным остальным, а выше их, специально для того, чтобы вытеснить все остальные, и чтобы переключения между потоками не происходило. А вы мне рассказываете, что нужно делать для того, чтобы переключения были частыми и потоки получали процессорное время поровну.
Кстати, приведенное Вами утверждение, по меньшей мере странное. Обоснуйте его, так как непонятно совсем, зачем "самостоятельно предусматривать слипы" в потоках с равными приоритетами для уравнивания между ними шансов на получение кванта? Неужели Вы считаете, что система не передаст управление другому потоку с таким же приоритетом по истечению кванта первого? |
|
18-07-2008 00:582Ins, ваше замечание о приоритетах некорректно. Оно следует из чтения документации, но в жизни все не так.
Посмотрите на dtf.ru статью о многозадачности Windows.
Для поддержки быстрого реагирования на проиходящие события в потоке, нужно оставить приоритет потоков равным, но самостоятельно предусмотреть частые регулярные "слипы", отдающие часть процессорного времени ядру системы. Тогда система будет чаще переключаться между потоками, чтобы "раздать" всем времени поровну. |
|
16-07-2008 03:49По моему мнению эта статья просто вольный перевод справки. Не умножайте сущности сверх необходимого
Точно установлено, что восприятие текста на родном языке происходит быстрее даже у того, кто очень хорошо владеет иностранным. А если его знаешь плохо, то приходится изучать два языка сразу - программирования и английский. Так что подобные статьи служат для облегчения этого процесса, причем когда они дополнены примерами, описаниями "подводных камней" - они гораздо полезнее справки. А help он всегда под рукой, в нужный момент можно и туда обратиться. |
|
16-07-2008 03:43>>> Выход из критической секции должен быть выполнен в любом случае, если этого не сделать, ваше приложение намертво повиснет при следующей попытке входа в критическую секцию.
Кстати, оно намертво повиснет и в противном случае: если выйти больше раз, чем зашли.
Например:
CS.Enter;
try
if WorldCollapseHappens then
begin
CS.Leave; // некоторые считают (неверно), что при Exit блок finally не выполнится => двойное освобождение
Exit;
end;
finally
CS.Leave;
end;
...
CS.Enter; // <= deadlock
P.S. На Vista поведение критических секций изменено и там такого нет. |
|
16-07-2008 03:29сообщение от автора материала Обрамлять их секцией try..finally..end - жизнено необходимо!
Вот это забыл упомянуть, каюсь. Следующий раз буду внимательней :)
И про приоритеты наверное стоило подробней расписать.
|
|
16-07-2008 03:11Про приоритеты и то, как их выбирать, я бы описал подробнее.
Ставить высокие приоритеты потокам не стоит, если этого не требует задача, так как это сильно нагружает систему.
Я бы сказал так:
1. Если ваш поток выполняется достаточно продолжительные действия, которые необходимо выполнять, так сказать в фоновом режиме, то такому потоку лучше выставить приоритет пониже. Так как в противном случае, он будет забирать у других потоков много процессорного времени, что скажется на общей производительности. Если же его приоритет будет низким, то фоновые действия будут выполняться только тогда, когда системе больше нечем заняться более важным.
2. Если ваш поток выполняет непродолжительные действия, однако требует от системы максимальной отзывчивости, т.е. эти действия нужно выполнять сразу при определенном условии и как можно быстрее, то потоку имеет смысл поставить приоритет повыше. Так как в противном случае, поток сможет выполнить свои действия только тогда, когда в системе не будет других задач, как большой, так и средней важности. Если же приоритет поставить высокий, то поток немедленно вытеснит все задачи с меньшим приоритетом и выполнит свои действия. А так как время их выполнения невелико, то на производительности системы это не скажется отрицательно - поток пробудился, забрал на себя процессорное время, быстро выполнил свои действия и вернул ресурс системе.
И по поводу критических секций небольшое замечание... Обрамлять их секцией try..finally..end - жизнено необходимо! :) И это даже важнее, чем обрамлять таким же образом Create/Free объектов. Выход из критической секции должен быть выполнен в любом случае, если этого не сделать, ваше приложение намертво повиснет при следующей попытке входа в критическую секцию.
|
|
16-07-2008 02:22По моему мнению эта статья просто вольный перевод справки. Не умножайте сущности сверх необходимого. |
|
15-07-2008 16:06сообщение от автора материала Хорошо бы укоротить строки в коде, а то приходится двигать текст по горизонтали.
Там просто одна строка должна за пределами кода быть, а попала в код.
толстый намёк на необходимость продолжения :)
Спасибо, будем стараться :) |
|
15-07-2008 15:17Хорошо бы укоротить строки в коде, а то приходится двигать текст по горизонтали.
Статья безусловно полезная, но всё-таки мало внимания уделено опасностям которые подстерегают программиста многостаночника :)
Было бы не плохо упомянуть, что при использовании TBitmap в потоке надо обязательно использовать Lock/Unlock var B: TBitmap;
begin
...
B.Canvas.Lock;
B.Canvas.Unlock; Еще очень распространен вопрос на КС, "почему запрос хоть и выполняется в потоке всё равно тормозит основную программу?". Но это, конечно, не укор автору, а толстый намёк на необходимость продолжения :) |
|
15-07-2008 11:05>>> Даа, там целая книга. Спасибо за ссылку, полезный материал.
Жаль только, что перевели частично, да и забросили вроде (2005-го года обновление). |
|
15-07-2008 10:30
15-07-2008 10:25сообщение от автора материала а чем использование объекта TThread лучше API винды?
Вы имеете ввиду CreateThread/BeginThread? Собственно, TThread есть просто "обёртка" над этими функциями, ведь в любом случае новый поток создаёт ОС, без их вызова не обойтись. Просто с ним удобней работать. |
|
15-07-2008 10:25TMultiReadExclusiveWriteSynchronizer спасет отца русской демократии... :) |
|
15-07-2008 10:24Пардон. Я всегда использую Windows.TRtlCriticalSection, даже не могу точно сказать, почему :) Видимо, в примере, по которому я учился, было так.
Пожалуй, исчерпывающий (но и объёмный) материал о потоках: http://forum.vingrad.ru/forum/topic-60076.html |
|
15-07-2008 10:18>>> а чем использование объекта TThread лучше API винды?
Удобством. Примерно тем же, чем TFileStream "лучше" CreateFile/ReadFile и т.п. |
|
15-07-2008 10:07а чем использование объекта TThread лучше API винды? |
|
15-07-2008 10:03Пользительно. Сам до этого докапывался, а тут всё и сразу. Критические секции вообще не использовал, теперь буду. Спасибо за статью. |
|
15-07-2008 09:10сообщение от автора материала Спасибо за комментарий, принимаю Ваши замечания, за исключением последнего: в SyncObjs объявлено описание TCriticalSection, поэтому и упомянул. |
|
15-07-2008 08:24Мелкие замечания:
1. Параллельность - на компьютерах с несколькими процессорами и/или многоядерном процессоре.
2. Resume - не запуск, а пробуждение спящего потока.
3. Неясно, к чему упомянут SyncObjs.
Думаю, начинающим для первого ознакомления вполне подойдёт ;-) |
|
|
|