В многозадачной (многопоточной) среде возникает проблема одновременного доступа к одним и тем же ресурсам (данным) со стороны нескольких потоков. В случае если конкретный ресурс не допускает такого использования, возникает конфликт, требующий разрешения. Это называют задачей синхронизации (взаимного исключения).
Критический ресурс – ресурс (объект, данные), который в силу своей физической природы либо логики использования не может быть доступен одновременно нескольким пользователям. В зависимости от контекста речь может идти об определенном сочетании обращений или любых обращениях вообще (например, одновременное чтение допустимо, но одновременная запись или чтение и запись – нет).
Примерами критических ресурсов могут служить устройство вывода (одновременно выводимые данные нескольких источников будут перемешаны), записываемый файл (чтение до окончания записи дает неверные результаты), счетчик цикла (постороннее изменение нарушает работу цикла) и так далее.
Критическая секция – участок кода, выполняющий обращение или логически связную последовательность обращений к критическому ресурсу. В зависимости от контекста может быть удобнее представлять, что критическая секция одна и определяется связью ее с критическим ресурсом, либо однотипные критические секции принадлежат различным пользователям, но связаны посредством единого критического ресурса.
В этих терминах задача исключения сводится к обеспечению единственности пользователя, находящегося внутри критической секции, связанной с данным критическим ресурсом.
Проблема усугубляется еще и тем, что проверка условий возможности доступа должна быть неотделима от самого доступа или хотя бы блокировки его для других пользователей, то есть атомарность. Так как прикладная программа обычно не может обеспечить это самостоятельно, многозадачные системы обязательно предоставляют механизмы синхронизации.
Простейшее средство исключения в Windows – объект CriticalSection. Он представляет собой обычную структуру и не имеет глобальной идентификации, поэтому может использоваться только потоками одного процесса. Использование CriticalSection выглядит в общем случае следующим образом:
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
…
EnterCriticalSection(&cs);
… //защищенная критическая секция
LeaveCriticalSection(&cs);
…
DeleteCriticalSection(&cs);
Вызов EnterCriticalSection – попытка входа в критическую секцию. Если секция уже занята другим потоком, то поток блокируется до момента ее освобождения. Повторное вхождение в критическую секцию допускается только для одного и того же потока, что помогает избегать самоблокировок. Блокировка управляется счетчиком, каждое вхождение в секцию увеличивает счетчик, освобождение – уменьшает, поэтому их количество должно быть сбалансировано.
Для синхронизации потоков разных процессов предусмотрены специальные объекты: Event, Mutex, Semaphore, WaitableTimer. Все они являются именованными и допускают глобальную идентификацию по именам.
Сам перевод потока в состояние ожидания осуществляется функциями WaitForSingleObject(), WaitForMultipleObjects() и их разновидностями. В зависимости от параметров эти функции блокируют выполнение потока до обнаружения одного или нескольких переданных им объектов в состоянии «готовности» (signaled).
Объект «событие» (Event) – наиболее простая разновидность. Объекты создаются функцией CreateEvent() и бывают двух типов: «ручные» и «автоматические». Открытие существующего объекта «событие» по его имени – OpenEvent(). Установка события (перевод в состояние signaled) происходит всегда явно функцией SetEvent(), сброс – для «ручных» событий явно, функцией ResetEvent(), для «автоматических» – автоматически при успешном выполнении Wait-функции. Также имеется функция PulseEvent() – временная установка события, активизация всех ожидавших его потоков и автоматический сброс. Кроме того, объекты Event ассоциируются с файлами при организации асинхронного ввода-вывода.
Объект «мьютекс» (Mutex) – простейший двузначный семафор для организации критических секций. Принято считать, что он «захватывается» потоком, и если мьютекс в этот момент уже занят, очередной захватывающий его поток блокируется. Подобно CriticalSection, Mutex допускает повторный захват, но только одним и тем же потоком. Создание нового мьютекса – вызов CreateMutex(), открытие существующего по имени – OpenMutex(), освобождение – ReleaseMutex(), захват с возможной блокировкой – Wait-функции.
Объект «семафор» (Semaphore) – отличается от мьютекса тем, что является счетчиком и может принимать множество значений от нуля и выше. «Занятым» считается семафор с нулевым значением, с ненулевым – свободным. При попытке опустить значение ниже нуля происходит блокировка. Работа семафора не зависит от того, разные потоки обращаются к нему или один и тот же. Создание нового семафора – вызов CreateSemaphore(), открытие существующего по имени – OpenSemaphore(), «подъём» счетчика (разблокирование) – ReleaseSemaphore(), проверка и «опускание», в том числе блокирование – Wait-функции.
Кроме специализированных объектов синхронизации Wait-функции могут работать также и с другими объектами, например:
– процессы и потоки – ожидание завершения;
– файлы – ожидание окончания текущей операции, и так далее.
Контрольные вопросы
1. Что такое синхронизация доступа к ресурсам и зачем она нужна.
2. Объекты синхронизации в Win 32.
3. Объект синхронизации CriticalSection, его использование.
4. Функция WaitForSingleObject, ее параметры и возвращаемые значения. Использование данной функции.
5. Объект синхронизации Event, его создание, уничтожение и использование. Параметры данных функций.
6. Объекты Event с автоматическим сбросом.
7. Объект синхронизации Mutex, его создание, уничтожение и использование. Параметры данных функций.
8. Объект синхронизации Semaphore, его создание, уничтожение и использование. Параметры данных функций. Особенности данного объекта синхронизации.
9. Отличие объекта синхронизации CriticalSection от объекта синхронизации Event, Mutex, Semaphore.
Варианты заданий
В каждом из заданий необходимо создать несколько потоков и защищенный ресурс. Каждый из потоков должен делать следующее: проверить, свободен ли защищенный ресурс; если занят, то дождаться освобождения; если свободен, то занять его, выполнить какие-то действия (указанные в задании), сделать паузу на одну секунду и освободить ресурс. Если в задании указано два объекта синхронизации, то необходимо выполнить отдельную программу для каждого из них.
12.3.1. Каждый из трех потоков должен пытаться закрасить главное окно в свой цвет: первый – в синий, второй – в красный и третий – в зеленый. В результате каждую секунду цвет фона главного окна будет изменяться. Реализовать синхронизацию доступа к ресурсам через Event, а затем через CriticalSection.
12.3.2. Каждый из трех потоков должен пытаться закрасить главное окно в свой цвет: первый – в желтый, второй – в голубой и третий – в черный. В результате каждую секунду цвет фона главного окна будет изменяться. Реализовать синхронизацию доступа к ресурсам через Mutex, а затем через Semaphore.
12.3.3. На главном окне необходимо создать Edit. Каждый из трех потоков должен пытаться установить в данный Edit соответствующий текст: First, Second или Third. Реализовать синхронизацию доступа к ресурсам через Event, а затем через CriticalSection.
4. На главном окне необходимо создать Edit. Каждый из трех потоков должен пытаться установить в данный Edit соответствующий текст: String1, String2, String3. Реализовать синхронизацию доступа к ресурсам через Mutex, а затем через Semaphore.
12.3.5. На главном окне необходимо нарисовать движущуюся справа налево фигуру (например квадрат). Также необходимо создать два потока: первый из них будет опускать фигуру вниз, а второй – поднимать вверх. Синхронизацию доступа к ресурсам реализовать через Event, а затем через CriticalSection.
12.3.6. На главном окне необходимо нарисовать движущуюся сверху вниз фигуру. Также необходимо создать два потока: первый из них будет смещать фигуру влево, а второй вправо. Реализовать синхронизацию доступа к ресурсам через Mutex, а затем через Semaphore.
12.3.7. Создать четыре потока, каждый из которых будет пытаться вывести в центре окна свой текст: AAAA, BBBB, CCCC, DDDD. Реализовать синхронизацию доступа к выводу на окно через Event, а затем через CriticalSection.
12.3.8. Создать четыре потока, каждый из которых будет пытаться вывести в центре окна свой текст: XXXX, ZZZZ, TTTT, YYYY. Реализовать синхронизацию доступа к выводу на окно через Mutex, а затем через Semaphore.
12.3.9. Создать три потока, каждый из которых будет пытаться вывести в центре окна свой рисунок: звездочку, квадратик, закрашенный эллипс. Реализовать синхронизацию доступа к выводу на окно через Event, а затем через CriticalSection.
12.3.10. Создать три потока, каждый из которых будет пытаться вывести в центре окна свой рисунок: домик, дерево, ромбик. Реализовать синхрониизацию доступа к выводу на окно через Mutex, а затем через Semaphore.
12.3.11. Создать на окне элемент управления ListBox. Также создать два потока, каждый из которых будет добавлять в данный ListBox свой текст: First или Second. Реализовать синхронизацию доступа к ListBox через Event, а затем через CriticalSection.
12.3.12. Создать на окне элемент управления ListBox. Также создать два потока, каждый из которых будет добавлять в данный ListBox свой текст: First или Second. Реализовать синхронизацию доступа к ListBox через Mutex, а затем через Semaphore.
12.3.13. Создать три потока, каждый из которых будет двигать по окну слева направо паровозик. В каждый момент доступ к выводу на окно должен иметь только один поток. Реализовать синхронизацию доступа к выводу на окно через Event, а затем через CriticalSection.
12.3.14. Создать пять потоков, каждый из которых будет двигать по окну слева направо паровозик. В каждый момент доступ к выводу на окно должны иметь два потока. Реализовать синхронизацию доступа к выводу на окно через Semaphore.
12.3.15. Реализовать восемь потоков, каждый из которых рисует постепенно удлиняющийся луч. Все лучи должны исходить из одной точки и быть направлены под разными углами. В каждый момент должны двигаться только три луча. Реализовать синхронизацию доступа к выводу на окно через Semaphore.
Лабораторная работа №13
Приоритеты
Цели работы:
1) изучить систему приоритетов;
2) изучить средства управления приоритетами в ОС Windows;
3) практически изучить влияние приоритетов на выполнение приложений и способы управления выполнение приложений посредством приоритетов.