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


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

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

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

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

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

Проектирование класса Thread-Safe

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

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

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

В следующем примере кода демонстрируется этот метод с помощью элемента 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, не имеет возможности запроса на блокировку по времени. Период ожидания, когда поток станет свободным, является бесконечным.

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

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

См. также

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