Шаг 67.
Среда программирования Visual C++.
Синхронизация потоков

    На этом шаге мы рассмотрим вопросы, связанные с доступам к данным.

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

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

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

    Одним из способов синхронизации потоков является использование глобального объекта, выполняющего роль посредника между потоками. В таблице 1 перечислены имеющиеся в MFC классы синхронизации, производные от базового класса CSyncObject. Они позволяют координировать асинхронные события любого вида.

Таблица 1. MFC-классы синхронизации
Имя Описание
CCriticalSection Разрешает доступ к объекту только одному потоку в пределах текущего процесса
CMutex Разрешает доступ к объекту только одному потоку из любого процесса
CSemaphore Разрешает одновременный доступ к объекту одному или нескольким потокам, число которых не должно превысить заданное максимальное значение
CEvent Оповещает приложение о возникновении события

    Классы синхронизации, применяемые совместно с классами синхронизации доступа CSingleLock и CMultiLock, обеспечивают безопасное обращение к глобальным данным и совместный доступ к ресурсам. Рекомендуются следующие правила работы с этими классами.

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

    Приведенный ниже пример иллюстрирует процедуру обеспечения безопасного доступа к глобальным данным. Для защиты доступа к объекту CTime, содержащемуся в классе CTimer, применяются объекты CCriticalSection и CSingleLock.

    Вызов CSingleLock::Unlock() в этих функциях не обязателен - он помещен здесь для соблюдения хорошего стиля программирования.

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

    Текст измененного приложения можно взять здесь (66,7 Кб).

    Со следующего шага мы начнем знакомиться с организацией контекстно-зависимой справки.




Предыдущий шаг Содержание Следующий шаг