Визуальное программирование и MFC



Использование критических секций - часть 2


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

Однако есть и другой способ корректного использования разделяемых переменных. Если необходимо, чтобы несколько потоков использовали глобальные переменные или другие разделяемые ресурсы, то для этого подходит такое средство синхронизации как критические секции. События хорошо подходят для “сигнализации”, а критические секции (разделы или секции кода, требующие монопольного доступа к разделяемым данным) удобны для управления доступом к данным.

Допустим, программа отслеживает показания времени как часы, минуты и секунды, а каждое из этих значений хранится в отдельной целочисленной переменной. Теперь представим, что значения времени совместно используются двумя потоками. Поток А изменяет значение времени и прерывается потоком Б после обновления часов, но до обновления минут и секунд. Результат: поток Б получает недостоверные показания времени.

Если для данного формата времени создается класс C++, то можно легко управлять доступом к данным, сделав элементы данных закрытыми и предусмотрев открытые функции-члены. Именно таков класс CHMS, рассматриваемый ниже. Следует отметить: в этом классе есть элемент данных типа CRITICAL_SECTION. (Здесь не применяется поддержка из MFC.) Конструктор вызывает Win32-функцию InitializeCriticalSection, а деструктор — DeleteCriticalSection. Таким образом, с каждым объектом CHMS связан объект критическая секция.

#include "stdafx.h" class CHMS { private: int m_nHr, m_nMn, m_nSc; CRITICAL_SECTION in_cs; public: CHMS() : m_nHr(0), m_nMn(0), m_nSc(0) { ::InitializeCriticalSection(&m_cs); } ~CHMS() { ::DeleteCriticalSection(&m_cs); } void SetTime(int nSecs) { ::EnterCriticalSection(&m_cs): m_nSc = nSecs % 60; m_nMn = (nSecs / 60) % 60; m_nHr = nSecs / 3600; ::LeaveCriticalSection(&m_cs); } void GetTotalSecs() { int nTotalSecs; ::EnterCriticalSection(&m_cs); nTotalSecs = m_nHr * 3600 + m_nMn * 60 + m_nSc; ::LeaveCriticalSection(&m_cs); return nTotalSecs; } void IncrementSecs() { ::EnterCriticalSection(&m_cs); SetTime(GetTotalSecs() + 1): ::LeaveCriticalSection(&m_cs); } };

Обратим внимание, что функции-члены вызывают функции EnterCriticalSection и LeaveCriticalSection. Если поток А исполняется в середине SetTime, поток Б будет блокирован вызовом EnterCriticalSection в GetTotalSecs до тех пор, пока поток А не вызовет LeaveCriticalSection. Функция IncrementSecs вызывает SetTime, что означает наличие вложенных критических секций. Это допустимо, так как Windows отслеживает уровни вложения.

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




Содержание  Назад  Вперед