Графика для Windows средствами DirectDraw
Шрифт:
С другой стороны, многопоточное приложение труднее написать и отладить. Вам придется синхронизировать многопоточный доступ к совместным ресурсам, чтобы избежать непредсказуемых результатов, а также координировать выполнение взаимозависимого кода, чтобы обеспечить правильную последовательность событий.
И последнее замечание: на однопроцессорном компьютере многопоточные приложения работают не быстрее однопоточных. Скорость возрастает лишь на многопроцессорном компьютере с многопроцессорной операционной системой (например, Windows NT).
Добавить новый поток в программу несложно — намного сложнее организовать его выполнение и завершение, поэтому многие функции многопоточных API предназначены именно для синхронизации потоков. В этом разделе мы кратко рассмотрим такие средства синхронизации.
Потоки координируются с помощью событий (events), которые передают информацию о состоянии одного или нескольких потоков. Событие может быть установленным (signaled) или сброшенным (unsignaled). Конкретный смысл событий может быть разным, но обычно они сигнализируют о блокировке потока.
Блокировку потока можно представить себе в виде цикла, непрерывно опрашивающего некоторую логическую переменную. Цикл продолжается до тех пор, пока переменная не примет значение TRUE. С технической точки зрения это не совсем точно, потому что заблокированный поток не производит активных опросов события. Вместо этого он приостанавливается, а система удаляет его из списка активных потоков. Лишь после того как блокирующее событие перейдет в установленное состояние, выполнение потока возобновляется. Соответственно заблокированный поток почти не расходует процессорного времени.
Блокировка потоков чаще всего используется для защиты совместных ресурсов от одновременного доступа со стороны нескольких потоков. Мутекс (mutex, сокращение от mutually exclusive, то есть «взаимоисключающий») представляет собой объект, который может в любой момент времени принадлежать лишь одному потоку, гарантируя безопасность доступа к связанному с ним ресурсу. Когда мутекс принадлежит некоторому потоку, все остальные потоки, пытающиеся получить его в свое распоряжение, блокируются до освобождения мутекса.
Критические секции (critical section), как и мутексы, используются для предотвращения одновременного доступа к ресурсу со стороны нескольких потоков. Однако если мутекс может синхронизировать межпроцессные потоки, критическая секция ограничивается потоками одного процесса. Ограничение компенсируется скоростью — критическая секция работает быстрее, чем мутекс.
Семафоры (semaphore) тоже могут применяться для ограничения доступа к ресурсам, но в отличие от мутексов или критических секций семафор разрешает одновременный доступ со стороны нескольких потоков. Максимальное количество потоков, одновременно получающих доступ к ресурсу, определяется при создании семафора. Затем доступ предоставляется всем потокам до тех пор, пока их количество не достигнет заданного предела. Все остальные потоки, желающие получить доступ, блокируются до тех пор, пока один или несколько потоков
Для многопоточного программирования Windows можно выбирать между классами MFC и потоковыми функциями Win32. Microsoft рекомендует использовать в MFC-приложениях классы потоков. Для работы с потоками в MFC предусмотрены следующие классы:
• CWinThread
• CSyncObject
• CEvent
• CCriticalSection
• CMutex
• CSemaphore
• CSingleLock
• CMultiLock
Класс CWinThread представляет отдельный поток. Он присутствует во всех приложениях, потому что класс CWinApp (базовый для класса DirectDrawApp) является производным от CWinThread. Этот экземпляр класса CWinThread представляет основной поток приложения; чтобы добавить новые рабочие потоки, следует создать объекты CWinThread.
Класс CSyncObject является виртуальным. Непосредственное создание экземпляров этого класса не разрешается; он существует лишь для того, чтобы обеспечивать функциональные возможности производных классов. Класс CSyncObject является базовым для классов CEvent, CCriticalSection, CMutex и CSemaphore. Объекты синхронизации, представленные этими классами, рассматривались в предыдущем разделе.
Классы CSingleLock и CMultiLock применяются для блокировки потоков по состоянию одного или нескольких событий. Класс CSingleLock блокирует поток до установки конкретного события, а CMultiLock — до установки одного или всех событий из заданного набора.
Позднее в этой главе мы воспользуемся классами CWinThread, CEvent, CCriticalSSection и CMultiLock.
Решение проблемы курсора
Теперь мы знаем все необходимое и можем сосредоточиться на решении проблемы курсора. Чтобы курсор мыши обновлялся независимо от основного потока, мы воспользуемся отдельным рабочим потоком (я буду называть его потоком ввода). Прежде всего давайте поговорим о основном потоке.
Основной поток программы Cursor ведет себя почти так же, как и основные потоки всех остальных программ, рассмотренных нами. Он инициализирует DirectDraw, создает поверхности приложения, строит очередной кадр во вторичном буфере и переключает страницы для его отображения. Чтобы обеспечить работу потока ввода, нам придется возложить на основной поток следующие дополнительные задачи:
• создание и запуск потока ввода;
• обновление курсора перед каждым переключением страниц;