Синхронизация в многопоточных приложениях MFC - Взаимоисключающие блокировки

ОГЛАВЛЕНИЕ

Взаимоисключающие блокировки

Взаимоисключающие блокировки, как и критические секции, предназначены для защиты общих ресурсов от одновременного доступа. Взаимоисключающие блокировки реализованы внутри ядра, и поэтому для выполнения они переходят в режим ядра. Взаимоисключающая блокировка может выполнять синхронизацию не только между различными потоками, но и между различными процессами. Такая взаимоисключающая блокировка должна иметь уникальное время, чтобы она распознавалась другим процессом (такие взаимоисключающие блокировки называются именованными взаимоисключающими блокировками). MFC предоставляет класс CMutex для работы с взаимоисключающими блокировками. Взаимоисключающую блокировку можно использовать таким образом:

CSingleLock singleLock(&m_Mutex);
singleLock.Lock();    // пытаемся захватить общий ресурс
if (singleLock.IsLocked())  // мы сделали это

{
    // используем общий ресурс ...

    // После завершения позволяем другим потокам использовать ресурс
    singleLock.Unlock();
}

То же самое делается с помощью функций Win32 API:

// пытаемся захватить общий ресурс
::WaitForSingleObject(m_Mutex, INFINITE);

// используем общий ресурс ...

// После завершения позволяем другим потокам использовать ресурс
::ReleaseMutex(m_Mutex);

Взаимоисключающую блокировку также можно использовать для ограничения числа выполняющихся экземпляров одним экземпляром. Следующий код можно поместить в начало метода InitInstance (или WinMain):

HANDLE h = CreateMutex(NULL, FALSE, "MutexUniqueName");
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
    AfxMessageBox("Экземпляр уже выполняется.");
    return(0);
}

Чтобы обеспечить глобально уникальное имя, используйте Глобально уникальный идентификатор.

Семафоры

Чтобы ограничить число потоков, использующих общие ресурсы, следует использовать семафоры. Семафор – это объект ядра. Он сохраняет переменную счетчика, чтобы отслеживать число потоков, использующих общий ресурс. Например, следующий код создает семафор с помощью класса MFC CSemaphore, который может обеспечить то, что только максимум 5 потоков смогут использовать общий ресурс в данный период времени (это устанавливается в первом параметре конструктора). Предполагается, что ни один поток не захватил никаких ресурсов изначально (второй параметр):

CSemaphore g_Sem(5, 5);

Как только поток получает доступ к общему ресурсу, переменная счетчика семафора уменьшается на единицу. Если счетчик станет равным нулю, то все дальнейшие попытки использовать ресурс будут отвергаться до тех пор, пока хотя бы один поток, захвативший ресурс, не освободит его (другими словами, освободит семафор). Мы можем использовать классы CSingleLock и/или CMultiLock, чтобы ждать/захватить/освободить семафор. Мы также можем использовать функции API, как показано ниже:

// Пытаемся использовать общий ресурс
::WaitForSingleObject(g_Sem, INFINITE);
// Теперь счетчик использования семафора уменьшился на единицу

//... Используем общий ресурс ...

// После завершения позволяем другим потокам использовать ресурс
::ReleaseSemaphore(g_Sem, 1, NULL);
// Теперь счетчик использования семафора увеличился на единицу

Взаимодействие между дополнительными потоками и основным потоком

Если основной поток хочет сообщить дополнительному потоку о каком-либо действии, удобно использовать объект события. Но поступить наоборот было бы неэффективно и неудобно для пользователей, так как остановка основного потока для ожидания события замедляет работу приложения. В этом случае было бы правильно использовать определенные пользователем сообщения для взаимодействия с основным потоком. Такое сообщение должно быть адресовано определенному окну, что означает, что дескриптор такого окна должен быть виден вызывающим потокам (дополнительным потокам).

Чтобы создать определенное пользователем сообщение, сначала нужно определить идентификатор для этого сообщения (более правильно – определить само сообщение). Предположительно, такой идентификатор должен быть видим для основного потока и для дополнительных потоков:

#define WM_MYMSG WM_USER + 1

WM_USER+n сообщений должны быть уникальными в рамках класса окна, но не во всем приложении. Более безопасный способ (в смысле уникальности) – использовать сообщения WM_APP+n таким образом:

#define WM_MYMSG WM_APP + 1

Метод обработчика сообщения должен быть объявлен внутри объявления класса окна, которому (окну) сообщение будет адресовано:

afx_msg LRESULT OnMyMessage(WPARAM , LPARAM );

Конечно, должно быть определение нескольких методов:

LRESULT CMyWnd::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
    // Сообщение получено
    // Делаем что-нибудь ...
    return 0;
}

И в итоге, чтобы назначить обработчик определителю типа сообщения, макроопределение ON_MESSAGE нужно использовать внутри пар BEGIN_MESSAGE_MAP и END_MESSAGE_MAP:

BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
    ...
    ON_MESSAGE(WM_MYMSG, OnMyMessage)
END_MESSAGE_MAP()

Теперь дополнительный поток имеет дескриптор окна (которое принадлежит основному потоку) и может отправить ему определенное пользователем сообщение, как показано ниже:

UINT ThreadProc(LPVOID pParam)
{
    HWND hWnd = (HWND) pParam;
    ...
    // Отправляем сообщение окну основного потока
    ::PostMessage(hWnd, WM_MYMSG, 0, 0);

    return 0;
}

Автор: Arman S.

Загрузить исходный код - 47.7 KB