Синхронизация потоков

Использование в программе нескольких потоков одновременно может привести к возникновению ряда специфических проблем. Например, как предотвратить одновременный доступ двух потоков к одним и тем же данным? Что произойдет, если в тот момент, когда один поток еще не завершил процедуру обновления некоторых данных, другой поток предпринимает попытку эти данные считать? Почти наверняка данные, считанные вторым потоком, окажутся некорректными, поскольку лишь некоторая их часть была на данный момент обновлена.

Обеспечение корректной совместной работы потоков называется синхронизацией потоков. Рассмотрим средства синхронизации потоков.

Объекты синхронизации и классы MFC

Все классы MFC, реализующие механизм синхронизации, можно разделить на две категории.

· классы для синхронизации работы потоков;

· классы для контроля доступа к объекту синхронизации.

Для синхронизации работы потоков используются следующие классы:

CCriticalSection - реализует критическую секцию;

CEvent - реализует объект события;

CMutex - реализует исключающий семафор;

CSemaphore - реализует классический семафор.

Для контроля доступа используются следующие классы: CSingleLock и CMultiLock. Они контролируют доступ к объекту синхронизации и содержат методы, используемые для предоставления и освобождения таких объектов. Класс CSingleLock управляет доступом к одному объекту синхронизации, а класс CMultiLock - к нескольким объектам.

Когда какой-либо объект синхронизации создан, доступ к нему можно контролировать с помощью класса CSingleLock. Для этого необходимо сначала создать объект типа CSingleLock с помощью конструктора:

CSingleLock( CSyncObject* pObject, BOOL bInitialLock = FALSE );

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

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

Метод Lock() предназначен для получения доступа к объекту синхронизации. Если объект синхронизации в данный момент не захвачен другим потоком, функция Lock() передаст этот объект во владение данному потоку. Теперь поток может получить доступ к защищенным данным. Завершив обработку данных, поток должен вызвать метод Unlock(), который освобождает объект синхронизации, давая возможность другим потокам использовать ресурс.

При работе с классом CSingleLock общая процедура управления доступом к ресурсу такова:

· создать объект синхронизации (например семафор), который будет использоваться для управления доступом к ресурсу;

· с помощью созданного объекта синхронизации создать объект типа CSingleLock;

· для получения доступа к ресурсу вызвать метод Lock();

· выполнить обращение к ресурсу;

· вызвать метод Unlock(), чтобы освободить ресурс.

Важно уметь определять тот класс, который нужен для работы. Если приложение должно ждать некоторого события перед получением доступа, то нам нужен CEvent. Если к объекту будут иметь доступ несколько потоков из одного приложения и необходимы ограничения по количеству потоков, тогда нам нужен CSemaphore. Если к объекту будет иметь доступ только один поток и из одного приложения – то CCriticalSection. Если только один поток, но из разных приложений – то CMutex.

Рассмотрим, как создавать и использовать объекты синхронизации.