На этом шаге мы рассмотрим вопросы, связанные с доступам к данным.
Вторичные потоки, как правило, применяются для реализации асинхронной работы. Асинхронной называется такая операция, выполнение которой не зависит от внешних событий или действий. Рассмотренный на предыдущих шагах поток таймера выполняется в собственной последовательности кода, каждую секунду проверяет системное время и не ожидает никаких событий в первичном потоке приложения, которое, в свою очередь, продолжает работу, не ожидая завершения потока таймера.
В некоторых ситуациях этим асинхронным процессам требуется синхронизация, то есть координирование действий. Например, рассмотрим поток-планировщик, который из потоков-заданий созданных другими приложениями, формирует очередь на печать. Эти потоки сообщают планировщику о своем намерении встать в очередь, а планировщик оповещает находящееся в очереди задание о том, что пришло его время печати.
Другой сценарий по обеспечению синхронизации потоков связан с обновлением глобальных данных приложения. Допустим, в приложении-диспетчере первый поток обновляет некую структуру данных после снятия показаний с прибора, на котором установлены датчики. Другой поток считывает записанные в структуру данные и выводит их на экран. Теперь предположим, что этот поток пытается получить показания в тот самый момент, когда структура данных обновляется первым потоком. Результат - повреждение данных, причем последствия этого могут буть очень серьезными. Чтобы одновременно только один поток мог читать и модифицировать данные, необходимо обеспечить синхронизацию доступа потоков к глобальным данным.
Одним из способов синхронизации потоков является использование глобального объекта, выполняющего роль посредника между потоками. В таблице 1 перечислены имеющиеся в MFC классы синхронизации, производные от базового класса CSyncObject. Они позволяют координировать асинхронные события любого вида.
Имя | Описание |
---|---|
CCriticalSection | Разрешает доступ к объекту только одному потоку в пределах текущего процесса |
CMutex | Разрешает доступ к объекту только одному потоку из любого процесса |
CSemaphore | Разрешает одновременный доступ к объекту одному или нескольким потокам, число которых не должно превысить заданное максимальное значение |
CEvent | Оповещает приложение о возникновении события |
Классы синхронизации, применяемые совместно с классами синхронизации доступа CSingleLock и CMultiLock, обеспечивают безопасное обращение к глобальным данным и совместный доступ к ресурсам. Рекомендуются следующие правила работы с этими классами.
Инкапсуляция глобальных ресурсов и код синхронизации внутри класса, сооздающего безопасный поток, обеспечит централизованное управление доступом к ресурсу и поможет избежать тупиковой ситуации. Последняя возникает, когда несколько потоков ожидают друг от друга освобождения общего ресурса и не могут из-за этого продолжить свою работу. Обычно условия возникновения тупиковых ситуаций трудно воспроизводимы, поэтому нужно очень тщательно анализировать те части кода, где осуществляется работа с несколькими потоками, выявить места потенциального возникновения тупиковых ситуаций и предотвратить эту возможность.
Приведенный ниже пример иллюстрирует процедуру обеспечения безопасного доступа к глобальным данным. Для защиты доступа к объекту CTime, содержащемуся в классе CTimer, применяются объекты CCriticalSection и CSingleLock.
#include <afxmt.h>
CCriticalSection m_CS;
Рис.1. Добавление переменной m_CS
CTime CTimer::GetTime()
{
CSingleLock csl(&m_CS);
csl.Lock();
CTime time = m_time;
csl.Unlock();
return time;
}
void CTimer::SetTime(CTime time)
{
CSingleLock csl(&m_CS);
csl.Lock();
m_time = time;
csl.Unlock();
}
Рис.2. Добавление текстов функций
Вызов CSingleLock::Unlock() в этих функциях не обязателен - он помещен здесь для соблюдения хорошего стиля программирования.
Теперь соберите и запустите приложение. Вы не заметите никакой разницы в действиях программы, но Вы получите гарантию защищенности данных, инкапсулированных в любом экземпляре класса CTimer, при доступе к ним любого числа потоков.
Текст измененного приложения можно взять здесь (66,7 Кб).
Со следующего шага мы начнем знакомиться с организацией контекстно-зависимой справки.