| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Общие вопросы > Thread-safe, или как безопасно работать с потоками |
| Автор: mr.DUDA 27.8.2003, 11:53 | ||||
| Народ, кто-нибудь уже делал многопоточные приложения ? Нужно написать класс типа:
т.е. так, чтобы объекты классов, производных от CShareable можно было бы разделять между двумя и более потоками, например:
ЗЫ, может как-то можно сделать без мьютексов ? Или с критическими секциями (работают быстрее, чем мьютексы) ?? |
| Автор: Fantasist 29.8.2003, 19:31 | ||||||
Обычно используются критические секции. Правда Lock с тайм-аутом не очень удобно делать. Однако можно замутить что-то типа:
Mutex'ы делать не удобно. Для каждего экземляра класса надо создать уникальный mutex, а значит уникальное имя. Опять же, создавать mutex при каждом Lock'e как-то не красиво и не удобно. И в Unlock его надо удалять, а не Release'ать. Однако наиболее интересна концепция Lock'ирующих указателей. Типа:
Оператор-> следит за тем, чтобы к объекту не обращались бы не заблокировав его вначале. Если не хочеться терять эффективность, то проверку можно окружить #ifdef _DEBUG ... #endif Применять просто:
Можно еще добавить operator T*. Тогда в этом указателе можно будет блокировать не только экземпляры классов, а вообще любые переменные. |
| Автор: mr.DUDA 29.8.2003, 23:36 | ||||
| Fantasist, спасибо за отличную идею с lock_ptr !!! Пару вопросов (м.б. глупых): 1) а нельзя ли делать WaitForSingleObject для критических секций (внутри CRITICAL_SECTION есть интересная переменная LockSemaphore):
2) почему:
если не задать имя для объекта WinAPI при его создании (а передать NULL), то мьютекс будет без имени, но тоже в единственном экземпляре. |
| Автор: Fantasist 2.9.2003, 06:23 | ||||
Не знаю. Никогда не пробывал. Попробуй - нам расскажешь.
Да это я ошибся. Думал по привычной схеме: синхронизация потоков через ownership handle'а объекта синхронизации с помощью явных функций захвата владения и освобождения (критические секции), а mutex'ы используем как глобальный именованный синхронизатор. Для меня mutex'ы без имени были бесполезны. Поэтому твою схему сразу и не понял. |
| Автор: Ars 3.9.2003, 10:43 | ||||
Весьма удобно использовать следующее:
Позволяет использовать несколько CriticalSection и не париться с обработкой exception:
|
| Автор: mr.DUDA 3.9.2003, 12:07 | ||
| У меня есть несколько вопросов, Ars и Fantasist: 1) сколько таких объектов можно создавать (если в каждом - хотя бы по одной критической секции) с точки зрения WinAPI, т.е. не запарится ли винда держать в памяти тысячу проинициализированных критических секций (если учесть, что под каждую выделяется не просто кусок памяти, а еще и семафор - объект ядра) 2) как долго выполняется InitializeCriticalSection/DeleteCriticalSection, и не уйдет ли всё процессорное время только на операции с крит. секциями (если ставить на все объекты, используемые в программе, защиту потоков). В моей проге возможен одновременный доступ нескольких потоков к любому объекту (а их может существовать от сотни до тысячи - как мелких, так и толстых) 3) чем круче критическая секция обычного мьютекса (если в крит. секции также используются объекты ядра - семафоры, и ожидающие функции - WaitForSingleObject), тем более что в критическую секцию можно попробовать войти, но нельзя дождаться её освобождения (выхода др. потока из неё) в течение определенного интервала. ЗЫ, Fantasist, можно конечно
и так делать, но не проще ли мьютекс+WaitForSingleObject ? Или есть какая-то другая причина для использования только критических секций ? Нужно определиться, что именно выгоднее в данной ситуации (когда критично время выполнения и корректность работы с кучей объектов). ЗЫ(2), я пробовал с семафором внутри CRITICAL_SECTION - что-то не получается. Винда как-то хитро его использует. |
| Автор: Ars 3.9.2003, 13:44 | ||
| 1. Думаю, ограничения могут быть связаны только с максимальным количеством системных объектов, поэтому пока создаются - можно создавать... 2. В проге чаще выполняются не InitializeCriticalSection/DeleteCriticalSection, а EnterCriticalSection/LeaveCriticalSection (т.к. все объекты должны использовать одни и те же CriticalSection). У потока при ожидании накладные расходы минимальны. 3. В хелпе написано:
К тому же, в critical section реализован механизм предотвращения deadlock'ов при повторном входе потока в одну и ту же critical section. Может так и у mutex'ов? Не проверял. А Билли Гейтс запретил лазить в CRITICAL_SECTION. Говорит, что она only для системного использования P.S. IMHO для работы с большим количеством объектов лучше не перегружать объекты операциями синхронизации, а сделать менеджер объектов, который возьмет на себя эти функции. |
| Автор: Ars 3.9.2003, 16:11 | ||
| mr.DUDA, прошу прощения. Из-за узости мышления не совсем въехал в суть и допустил несколько неточностей... Для решения твоей задачи надо разобраться с несколькими вопросами: 1. Что необходимо синхронизировать - доступ к многочисленным объектам или к данным этих объектов? 2. Как долго выполняется код между вызовами Lock и Unlock? Некоторые соображения: Если необходимо синхронизировать доступ к объектам или их данным и обращения к ним носят характер чтения-записи (без длительной обработки), то с этим вполне может справиться менеджер объектов, используя только один синхро-объект по схеме: а) залочить список объектов; б) создать или удалить объект, прочитать или записать его данные; в) разлочить список объектов. Если есть длительная обработка, то может ее можно вынести в отдельный поток со всеми данными для нее? Ну а если все до кучи - видимо без синхро-объекта в каждом экземпляре класса не обойтись, но я с такими несчастными случаями не сталкивался. Вообще, во многих случаях можно обойтись следующим кодом. Грубо, но ни разу не глючило.
|
| Автор: Fantasist 3.9.2003, 18:31 | ||||||
В этом случае полезно подумать, а нельзя ли разбить объекты на функционально связанные группы и блокировать сразу группами. Типа транзакции.
Конечно, с mutex'ами также. С какой-то стороны они даже безопаснее, ибо в reference есть такое упоминание:
Про mutex'ов такого нет. Конечно, такая ситуация может возникнуть только в запутанной системе управления синхронизацией. На практике я не видел ничего близкого к подобной ситуации. И тем не менее. |
| Автор: mr.DUDA 3.9.2003, 21:49 |
| to Ars: 1) к данным любого объекта м.б. предоставлен доступ на чтение и запись, из любого потока 2) объекты упорядочены по иерархии (каждый объект имеет 2 списка дочерних объектов) 3) объектов м.б. очень много, и структура м.б. очень сложной 4) обработка данных объекта может занять определенное время (если например используются файловые или оконные функции для сохранения/чтения или отображения объекта) имхо, можно было бы сделать внутри каждого потока свою CRITICAL_SECTION, и в каждом объекте хранить указатель на CRITICAL_SECTION потока-владельца (тогда любой другой поток сначала подождет полного освобождения критической секции, прежде чем писать или читать объект). Только недостаток этого подхода в том, что если скажем мы заблокировали одним потоком доступ сначала к объекту X, а потом - к объекту Y, то другой поток не сможет получить доступа к объекту X, пока не будет полностью освобождена критическая секция (т.е. нужно освободить доступ как к X, так и к Y). С другой стороны, хранить внутри каждого объекта мьютекс или критическую секцию невыгодно. Менеджер объектов даст только лишнюю головную боль. |
| Автор: Ars 4.9.2003, 10:51 | ||
| mr.DUDA, да-а-а!.. Все равно, в жизни нет истинной многозадачности, ибо кол-во процессоров много меньше кол-ва потоков и какая разница, будет ли поток простаивать в ожидании освобождения сихронизирующего объекта или в ожидании своего процессорного времени. Поэтому, если время однократного прохода по трид-защищенному коду сравнимо с несколькими квантами времени для этого потока, то не стоит напрягаться по поводу блокировки сразу всех объектов на входе в суперструктуру и использовать один сихронизирующий объект. Для продолжительной обработки данных вполне подходит отдельный поток или асинхронные функции (что, в принципе, одно и то же). IMHO, вообще, грамотно разделять обработку данных на процессоре и медленный ввод-вывод. Не совсем понял идею
CRITICAL_SECTION - это как раз то, что у них должно быть общее. Как они будут подменять указатель на CRITICAL_SECTION в объекте? Что будет делать поток-владелец, когда его CRITICAL_SECTION поимеют другие (потоки), т.е. как он вернет ее себе? И вот еще. Стремно делать CRITICAL_SECTION (или другой синхрообъект с бесконечным ожиданием) в каждом объекте, если планируется залочивать более одного объекта одновременно, т.к. легко словить взаимную блокировку потоков. |
| Автор: mr.DUDA 4.9.2003, 17:03 |
| А если забить на многопотоковую модель и сделать 1 поток для выполнения списка действий ? А еще можно сделать 2-й поток, извлекающий из очереди сообщения одно за другим и посылающий их второму потоку-исполнителю; в случае "подвисания" второго потока, первый после таймаута перезапустит его. Так можно снять вообще проблему разделения объектов несколькими потоками. Суть решаемой проблемы - сделать возможной обработку т.н. "сообщений" (задаваемых числовым ID'ом и списком параметров) с "завёрткой" в тело сообщения указателей на объекты из общего для всех потоков дерева объектов. Для расширяемого (с пом. модулей) приложения нужно выбрать способ реакции внутри каждого модуля на "приходящее" в очередь сообщение: либо делать внутри каждого DLL свой поток обработки сообщений, либо делать внутри "главного модуля" (EXE-шки) один единственный поток (или 2, описанных выше), и при загрузке модуля регистрировать сообщения данного модуля так, чтобы при поступлении сообщения MSG_A вызывалась ф-ция из модуля MOD_A. Дерево объектов общее, т.к. собственно решается задача совместной работы множества подключаемых модулей над одним объектом-"документом". |
| Автор: Ars 15.9.2003, 11:11 |
| mr.DUDA, если это еще актуально, скинь мне на личку пересказ ТЗ в части описания пользователей (и/или внешних систем), их количества и их требований к скорости доступа к объектам. Подумаю на досуге... |