Поделиться через


Многопоточность. Использование классов синхронизации

Синхронизируемый доступ к ресурсам между потоками является общей проблемой при записи многопоточных приложений.При наличии одновременно двух или более потоков доступ к одним и тем же данным может привести к нежелательными и непредсказуемым результатам.К примеру, один поток может обновлять содержимое структуры, в то время как другой поток считывает содержимое той же структуры.При этом неизвестно какие данные будут получены считывающим потоком: старые данные или вновь записанные или возможно смесь тех и других.MFC предоставляет номер синхронизации и классы доступа синхронизации для помощи в решении этой проблемы.В этом разделе описываются доступные классы и способы их использования для создания потокобезопасных классов в типичном многопоточном приложении.

Типичное многопоточное приложение содержит класс, представляющий ресурс для совместного использования потоками.Спроектированные надлежащим образом полностью потокобезопасные классы не требуют вызова каких-либо функций синхронизации.Все обрабатывается внутри класса, позволяя тем самым сконцентрироваться на том, как лучше использовать класс, а не на том, как он может быть поврежден.Эффективной техникой создания полностью потокобезопасного класса является объединение класса синхронизации с классом ресурса.Объединение классов синхронизации в общедоступный класс является прямолинейным процессом.

В качестве примера следует взять приложение, поддерживающее связанный список учетных записей.Это приложение позволяет проверять в отдельных окнах до трех учетных записей, но в определенный момент времени может обновляться только одна учетная запись.При обновлении учетной записи обновленные данные отправляются по сети в архив данных.

В этом примере приложения используются все три типа классов синхронизации.Так как одновременно можно проверять до трех учетных записей, для ограничения доступа к трем объектам просмотра используется CSemaphore.При попытке просмотра четвертой учетной записи приложение находится в ожидании закрытия одного из первых трех окон или завершается с ошибкой.При обновлении учетной записи, для гарантии того, что в определенный момент времени обновляется только одна учетная запись, приложением используется CCriticalSection.После успешного обновления приложение сообщает CEvent, которое освобождает поток, ожидающий событие оповещения.Этот поток направляет новые данные в архив данных.

Разработка потокобезопасного класса

Чтобы сделать класс полностью потокобезопасным, сначала необходимо добавить соответствующий класс синхронизации к общедоступным классам в качестве элемента данных.В предыдущем примере управления учетными записями, элемент данных CSemaphore был бы добавлен в класс представления, элемент данных CCriticalSection — в класс связанного списка, а элемент данных CEvent — в класс хранения данных.

Далее необходимо добавить вызовы синхронизации ко всем функциям элемента, которые изменяют данные в классе или имеют доступ к контролируемому ресурсу.В каждой функции необходимо создать объект CSingleLock или CMultiLock и вызывать функцию Lock этого объекта.Когда объект блокировки выходит за область действия и разрушается, деструктор объекта вызывает Unlock освобождая ресурс.Конечно же, при желании можно вызывать Unlock непосредственно.

Разработанный подобным образом потокобезопасный класс можно использовать во многопоточном приложении так же легко, как и потокоопасный класс, но с высоким уровнем безопасности.Инкапсуляция в классе ресурса объекта синхронизации и объекта синхронизации доступа предоставляет все преимущества полностью потокобезопасного программирования без недостатка в виде поддержания кода синхронизации.

Этот метод демонстрируется в следующем примере кода с помощью использования элемента данных, m_CritSection (типа CCriticalSection), объявленного в классе общедоступного ресурса и объекта CSingleLock.Попытка синхронизации общедоступного ресурса (производного от CWinThread) осуществляется созданием объекта CSingleLock с помощью адреса объекта m_CritSection.Совершается попытка блокирования ресурса и, при его блокировании, совершается работа на общедоступном объекте.По окончании работы ресурс разблокируется с помощью вызова Unlock.

CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...

singleLock.Unlock();
ПримечаниеПримечание

CCriticalSection, в отличие от других классов синхронизации MFC, не имеет параметра запроса на блокировку по времени.Период ожидания для освобождения потока является бесконечным.

Недостатками этого подхода является то, что этот класс будет немного медленнее, чем такой же класс без добавления объектов синхронизации.Кроме того, если существует вероятность того, что более чем один поток может удалить объект, объединенный подход может не всегда работать.В этой ситуации лучше всего поддерживать отдельные объекты синхронизации.

Дополнительные сведения о том какой класс синхронизации лучше использовать в разных ситуациях см. в разделе Многопоточность. Использование классов синхронизации.Дополнительные сведения о синхронизации см. в разделе Синхронизация в Windows SDK.Дополнительные сведения о поддержке многопоточности в MFC см. в разделе Многопоточность с C++ и MFC.

См. также

Основные понятия

Реализация многопоточности на языке C++ с помощью классов MFC