Новости

Потоки в операційній системі Windows. Засоби синхронізації потоків. Засоби роботи з потоками. Об’єкт TThread систем Delphi та C++ Builder для роботи з потоками

Работа добавлена:






Потоки в операційній системі Windows. Засоби синхронізації потоків. Засоби роботи з потоками. Об’єкт TThread систем Delphi та C++ Builder для роботи з потоками на http://mirrorref.ru

Лекция 10. Потоки в операційній системі Windows. Засоби синхронізації потоків. Засоби роботи з потоками. Об’єкт TThread систем Delphi та C++ Builder для роботи з потоками. Функції API Windows для роботи з потоками. Локальна пам’ять потоків.

8.1. Общие сведения о потоках

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

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

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

Любой поток определяет последовательность исполнения кода в процессе и состоит из двух компонентов:

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

Любой поток принадлежит какому-либо процессу, который ничего не выполняет, а является контейнером (адресным пространством) для потоков. Потоки всегда создаются в контексте какого-либо процесса, и вся их жизнь проходит в его границах (адресном пространстве).Два и более потока, созданные в контексте одного процесса разделяют одно адресное пространство. Потоки могут исполнять один и тот же код и манипулировать одними и теми же данными, а так же совместно использовать описатели объектов ядра, поскольку таблица описателей принадлежит процессу, а не потоку. Потоку требуется объект ядра и стек, объём статических сведений о потоке невелик и занимает немного памяти, в отличие от процесса, который требует значительных системных ресурсов (в адресное виртуальное пространство процесса загружаются DLL и EXE файлы).Рекомендуется избегать создания новых процессов и решать необходимые задачи с помощью потоков.

При инициализации процесса система всегда создаёт первичный поток, функцией которого является входная функция (WinMain, wWinMain, main, wmain). Все остальные потоки будут дочерними по отношению к первичному потоку, и будут иметь более низкий приоритет. Если завершить первичный поток, то будут автоматически закрыты и дескрипторы дочерних потоков, следовательно, процесс будет закрыт.Любой дочерний поток имеет функцию вида:

DWORDWINAPIThreadFuncName(PVOIDpvParam )

{

     DWORD dwResult = 0;

     // . . .

     return (dwResult);

}

Функция потока может выполнять любые задачи, поставленные перед потоком, по завершении которых она вернёт управление вызывавшему ее потоку, поток остановится, память, отведённая под его стек будет освобождена, поток будет разрушен, счётчик пользователей объекта ядра уменьшается на 1.Функция дочернего потока получает единственный параметр, представляющий собой указатель на любой тип данных.Функция потока должна возвращать значение, которое будет использоваться как код завершения потока.Функциям потоков рекомендуется по мере возможности обходиться локальными переменными (они создаются в стеке потока) или использовать средства синхронизации потоков.

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

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

Рассмотрим решение, которое можно реализовать, используя возможности операционной системы. Предположим, что есть таблица с двумя колонками, таким образом, каждая запись имеет два поля: в первом поле содержится идентификатор ID-потока, а во втором — 32-битное число. Каждый раз при обращении потока к функции в этой функции определяется ID обратившегося потока при помощи функции GetCurrentThreadID и осуществляется его поиск в таблице. Если запись с таким идентификатором есть, то функция использует эту запись, в противном случае в таблицу вносится новая запись, содержащая ID текущего потока. Таким образом, получается таблица, принадлежащая текущему потоку, во втором поле этой записи можно хранить счетчик обращения данным потоком к функции.

По описанному выше принципу работаетлокальная память потоков (Thread Local Storage, TLS). Для каждого из процессов создается набор внутренних таблиц. Windows может создать для каждого процесса до 64 таких таблиц, таким образом можно использовать 64 различные переменные TLS. При обращении к TlsAlloc Windows выделяет одну таблицу TLS. После этого можно использовать функции TlsGetValue и TlsSetValue для того, чтобы установить или прочитать значение из таблицы. При этом операционная система обращается именно к той записи в таблице, которая соответствует текущему потоку. Если таблица больше не нужна, то можно обратиться к функции TlsFree для того, чтобы освободить ее. В каждой записи таблицы можно хранить любое 32-битное число, однако, чаще всего таблица TLS используется для хранения указателей на класс или структуру, содержащую все необходимые для потока переменные.

В последних версиях ОС Windows появилось такое понятие как нить. Преобразовать поток в нить можно при помощи функцииConvertThreadToFiber. Каждый поток может обладать несколькими нитями. Когда система переключается с одного потока на выполнение другого, начинается выполнение текущей нити для данного потока. Если в потоке только одна нить, то код, который ранее был частью потока, стал частью нити. Однако если в потоке несколько нитей, то будет возможность управлять переключением между ними при помощи функцииSwitchToFiber. Создать новую нить в текущем потоке можно при помощи функцииCreateFiber.

8.2. Синхронизация потоков

С реализацией многопоточности тесно связано понятиесинхронизация -  управление совместным доступом  различных потоков к одним и тем же ресурсам (данные, процессор, экранные формы, модемы, принтеры и т.д.). Этот процесс очень сложный и в ОС Windows существуют различные способы синхронизации, которые будут рассмотрены ниже. Все потоки в системе обычно имеют доступ к её ресурсам – файлам, блокам памяти, последовательным портам и т.д. Любой поток, запрашивая нужный ресурс, должен получить монопольный доступ к ресурсу, что и является одной из задач синхронизации потоков. Таким образом, основными задачами синхронизации потоков являются:

  • монопольный доступ к совместно разделяемому ресурсу, иначе потоки могут разрушить его;
  • уведомление некоторых потоков о завершении каких-либо операций в других потоках.

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

Ситуациягоноквозникает, когда два или более потока пытаются получить доступ к общему ресурсу и изменить его состояние. Предположим Поток 1 получил доступ к ресурсу и изменил его согласно сценария своей работы. После этого  был запущен Поток 2 и он также модифицировал этот же ресурс до завершения Потока 1. Поток 1 полагает, что ресурс остался без изменений, однако он уже изменен. В зависимости от того, когда именно был изменен ресурс и какой это был ресурс, результаты могут быть различными: либо вообще ничего не произойдет, либо приложение перестанет работать, либо какие–то данные будут потеряны.

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

В пользовательском режиме ОСWindows предоставляются средства синхронизации потоков, которые будут сейчас рассмотрены.

Атомарный доступ (atomic access) - монопольный захват ресурсов обращающимся к нему потоком, - это есть основа основ синхронизации потоков. Interlocked функции – самый простой способ синхронизации потоков за счёт атомарного доступа к переменной, эти функции гарантируют монопольный доступ к переменной типа LONG независимо от того, какой именно код генерируется компилятором и сколько процессоров имеет компьютер. Важной особенностью является то, что это самый быстрый способ синхронизации потоков из всех существующих. Вызов одной такой функции требует не более 50 тактов процессора, а при использовании синхронизирующих объектов ядра требуется переход в режим ядра, забирающий не менее 1000 тактов процессора и выход из режима ядра.Существуют следующиеInterlocked функции:InterlockedIncrement,InterlockedDecrement,InterlockedExchangeAdd,InterlockedExchange,InterlockedExchangePointer.ФункцииInterlockedExchange иInterlockedExchangePointer монопольно заменяют текущее значение переменной типа LONG, адрес которой передаётся в первом параметре, на значение, передаваемое во втором параметре.В 32-разрядном приложении обе функции работают с 32-разрядными значениями, но в 64-разрядном, - первая функция работают с 32-разрядными значениями, а вторая с 64-разрядными.ФункцияInterlockedExchangeAdd прибавляет значение второго параметра к первому.Все функции возвращают исходное значение переменной.Принцип работы Interlocked-функций состоит в следующем. Для процессоров семействаIntel x86 эти функции выдают по шине аппаратный сигнал, не давая другому процессору обратиться по этому адресу. Ддя процессоров Alpha устанавливается специальный битовый флаг процессора, указывающий, что данный адрес памяти сейчас занят, далее значение считывается из памяти в регистр и меняется в регистре, и если флаг не был сброшен, то записывается обратно в память.

Спин-блокировка (spin-lock). Это способ использования функцииInterlockedExchange для монопольного доступа к разделяемому ресурсу. Для решения этой задачи вводится переменная, которая используется как флаг, показывающий состояние разделяемого ресурса (свободен/занят). Если ресурс занят, то цикл будет работать и загружать процессор до тех пор, пока ресурс не освободится.Спин-блокировка обычно используется, когда защищаемый ресурс занят недолго, а также для реализации критических секций (critical section). Данный метод рассчитан на одинаковый приоритет потоков, работающих с разделяемым ресурсом, и динамическое изменение приоритетов потоков должно быть отключено.При использовании спин-блокировки нельзя допустить попадания флага занятости ресурса и защищаемых данных в одну кэш-линию, это может вызвать колоссальное снижение производительности на многопроцессорных машинах.

Кэш-линии процессора  (CPU cache lines) необходимо учитывать при создании высокоэффективных приложений для многопроцессорных машин. Когда процессору нужно считать из памяти один байт, он извлекает не только его, но и столько смежных байтов, сколько потребуется для заполнения кэш-линии. В зависимости от типа процессора, кэш-линии состоят из 32 или 64 байтов и всегда выравниваются по границам, кратным 32 или 64 байтам.  Приложение работает с набором смежных байтов, и если эти байты находятся в кэше, процессору не приходится снова обращаться к шине памяти, что будет означать повышение производительности. В многопроцессорной системе кэш-линии могут сильно уменьшить быстродействие приложения по следующим причинам:

1. CPU1 считывает байт и смежные с ним байты из оперативной памяти и заполняет свою кэш-линию;

2. CPU2 повторяет действия CPU1, после чего имеет свою копию данных в своей кэш-линии;

3. CPU1 модифицирует байт памяти, и записывает его в свою кэш-линию, после чего остальные копии данных в кэш-линиях других процессоров будут объявлены недействительными;

4. Остальным процессорам придется обновить свои кэш-линии и повторить операции после того, как CPU1 обновит оперативную память новым, модифицированным значением.

Из этого можно сделать следующие выводы. Необходимо группировать данные своего приложения в блоки размером с кэш-линии и выравнивать их по тем же правилам, которые применяются к кэш-линиям. Необходимо добиваться того, чтобы различные процессоры обращались к разным адресам памяти, отделённым друг от друга, по крайней мере, границей кэш-линии. Кроме того, необходимо отделить данные «только для чтения» или редко используемые данные от данных «для чтения и записи». Также необходимо группировать те блоки данных, обращение к которым происходит в одно и то же время. Если данные используются одним потоком (параметры функции и локальные переменные) или одним процессором (привязка потоков, использующих данные, к одному процессору или однопроцессорная система), то кэш-линии вообще влиять на скорость не будут и их можно не учитывать.

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

Критические секции (critical section) — это части исходного кода, которые не могут выполняться двумя потоками в одно и то же время. Используя критическую секцию, можно  упорядочить выполнение конкретных частей исходного кода. Критические секции могут использоваться только в пределах одного процесса, одного приложения.

Система может вытеснить поток и передать процессорное время другому потоку, но к защищённому ресурсу ни один поток доступа не получит, пока данный поток не выйдет из критической секции. Критические секции реализованы на основе Interlocked-функций и выполняются очень быстро, но их недостаток заключается в том, что они дают возможности синхронизировать потоки только в рамках одного процесса.Для каждого разделяемого ресурса используется отдельно выделенный экземпляр структуры CRITICAL_SECTION, который обычно является глобальным, и с которым оперируют специальные функции входа и выхода в/из критической секции. Перед использованием структуры она инициализируется функциейInitializeCriticalSection.

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

ФункцияEnterCriticalSectionпросматривает значения элементов структуры CRITICAL_SECTION. Если ресурс свободен, то модифицирует значения элементов структуры, указывая, что вызывающий поток занял ресурс.Если значения элементов структуры означают, что ресурс захвачен другим потоком, то функция переводит этот поток в режим ожидания и система запоминает, что он хочет получить доступ к ресурсу. Поток в состоянии ожидания не тратит процессорного времени.Если на многопроцессорной машине одновременно стартуют две функции EnterCriticalSection, принадлежащие одному ресурсу, то функции всё равно корректно справятся со своей задачей и один из потоков получит доступ к ресурсу, другой перейдет в состояние ожидания. Для входа в критическую секцию также может быть использована функцияTryEnterCriticalSection, котораяотличается от предыдущей функции тем, что не приостанавливает выполнение потока если ресурс занят, а возвращает FALSE.Если ресурс свободен, то функция модифицирует структуру, т.е. захватит ресурс и вернёт TRUE.

Выход из критической секции осуществляется функциейLeaveCriticalSection. Функция просматривает элементы структуры и уменьшает счётчик захватов ресурса на 1, если его значение равно 0, то управление будет передано ожидающему потоку в функцииEnterCriticalSection.Если один и тот же поток вызывал два раза EnterCriticalSection для одного ресурса, то он должен вызвать и два раза LeaveCriticalSection.После окончания использования критической секции, её нужно удалить при помощи функцииDeleteCriticalSection.

При попытке потока войти в критическую секцию, занятую другим потоком, он немедленно приостанавливается, переходя из пользовательского режима в режим ядра. На многопроцессорной машине поток, захвативший ресурс и поток, переходящий в режим ожидания (в режим ядра), могут выполняться на разных процессорах. Тогда появится вероятность того, что ресурс будет освобождён ещё до того, как второй поток перейдёт в режим ядра. Система позволяет задать количество циклов спин-блокировки перед тем, как поток перейдет в режим ожидания (режим ядра). Для этого можно использовать функциюInitializeCriticalSectionAndSpinCount илиSetCriticalSectionAndSpinCount. Параметром этих функций является счетчик блокировок, возможное значение которого лежит в пределах от 0 до 0x00FFFFFF. Наоднопроцессорной машине значение параметра этих функций всегда равно нулю, т.к. поток, владеющий ресурсом, не сможет освободить его, пока другой поток крутится в циклах спин-блокировки.

ФункцияEnterCriticalSection переводит поток в режим ожидания на определённое время, после чего генерируется исключение. Длительность ожидания определена в ключе

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager.

По умолчанию оно равно 2 592 000 секунд (30 суток). Если [VOID InitializeCriticalSection(…)] потерпит неудачу, то будет выброшено исключение STATUS_NO_MEMORY, перехватить которое можно структурной обработкой исключений. Функция [BOOL InitializeCriticalSectionAndSpinCount(…)] в этом случае вернёт FALSE. Во время работы критической секции может возникнуть ситуация, когда два и более потоков конкурируют за доступ к ресурсу. При этом критическая секция создаёт объект ядра «событие» (event kernel object). Если к такой ситуации ещё добавится и большая нехватка памяти, то функция EnterCriticalSection сгенерирует исключение EXCEPTION_INVALID_HANDLE. Можно использовать структурную обработку исключений и перехватывать ошибку, при этом придётся дождаться появления свободной памяти.

Второй способ решения этой проблемы – создать критическую секцию функциейInitializeCriticalSectionAndSpinCount, передавая в параметр этой функции значение, у которого старший бит установлен. Таким образом память под объект ядра «событие» будет выделена заранее.

Рассмотрим теперь практическое использование критических секций.Для нескольких независимых разделяемых ресурсов создаются отдельные критические секции или точнее, экземпляры структуры CRITICAL_SECTION.

Пример 8.1  Использование критических секций

int iMyRes1;

charcMyRes2;

CRITICAL_SECTION csResource1;

CRITICAL_SECTION csResource2;

DWORD WINAPI ThreadFunc(PVOID pvParam)

{

   EnterCriticalSection(&csResource1);

   //доступкресурсу iMyRes1

   LeaveCriticalSection(&csResource1);

   // -------------выполняетсякодпотока----------------

   EnterCriticalSection(&csResource2);

   //доступкресурсу cMyRes2

   LeaveCriticalSection(&csResource2);

   // -------------выполняетсякодпотока----------------

   EnterCriticalSection(&csResource1);

   EnterCriticalSection(&csResource2);

   //одновременныйдоступкнесколькимресурсам (iMyRes1и cResource2)

   LeaveCriticalSection(&csResource2);

   LeaveCriticalSection(&csResource1);

}

Ситуациявзаимнойблокировки (dead lock)можетвозникнутьвследствиенесоблюдениипорядкавхождениявкритическиесекции:

DWORD WINAPI ThreadFunc1(PVOID pvParam)

{

   EnterCriticalSection(&csResource1); //доступкресурсу iMyRes1

   EnterCriticalSection(&csResource2); //доступкресурсу cMyRes2

// одновременный доступ к нескольким ресурсам (iMyRes1 и cMyRes2)

LeaveCriticalSection(&csResource2);

   LeaveCriticalSection(&csResource1);

}

DWORD WINAPI ThreadFunc2(PVOID pvParam)

{

   EnterCriticalSection(&csResource2); //доступкресурсу cMyRes2

   EnterCriticalSection(&csResource1); //доступкресурсу iMyRes1

// одновременный доступ к нескольким ресурсам (iMyRes1 и cMyRes2)

LeaveCriticalSection(&csResource2);

LeaveCriticalSection(&csResource1);

}

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

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

8.2.1. Синхронизация потоков объектами ядра

Механизмы синхронизации в пользовательском режиме (рассмотренные выше) обеспечивают высокое быстродействие, но эти механизмы имеют много ограничений, и в большинстве приложений их недостаточно. Механизмы синхронизации объектами ядра предоставляют большую функциональность, единственный их недостаток – это меньшее быстродействие.

Некоторые объекты ядра, как синхронизируемые, так и синхронизирующие могут находиться в двух состояниях: свободном (signaled state) и занятом (nonsignaled state). Переход из одного состояния в другое осуществляется по правилам, определённым для каждого из объектов ядра отдельно. Внутри объекта ядра находится булева переменная, которая хранит состояние объекта ядра (FALSE – занят, TRUE – свободен). Объект ядра «процесс»/«поток» пребывает в занятом (FALSE) состоянии, пока выполняется сопоставленный с ним процесс/поток, и переходит в свободное (TRUE) состояние, когда процесс/поток завершается. Кроме того, потоки, в отличие от процессов, могут существовать в свободном (TRUE) состоянии не завершаясь, а засыпая. Для введения потока в свободное (TRUE) состояние существуют Wait-функции. Они переводят потоки из режима пользователя в режим ядра.В двух состояниях могут находиться такие объекты ядра как:процессы, потоки, задания, события, семафоры, мьютексы, ожидаемые таймеры, файлы, консольный ввод, уведомления об изменении файлов.

К объектам ядра, которые можно использовать для синхронизации относятся:

Мъютексы (mutex) — это глобальные объекты, при помощи которых возможно организовать последовательный доступ к ресурсам. Сначала устанавливается мьютекс, затем происходит некоторое действие и после этого освобождается мьютекс. Если мьютекс установлен, любой другой поток (или процесс), который попытается установить тот же мьютекс, будет остановлен до того момента, когда мьютекс будет освобожден предыдущим потоком (или процессом). Мьютекс может совместно использоваться различными приложениями.

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

При работе с мьютексами необходимо следовать следующим правилам:

  1. Если его идентификатор потока равен 0 (поток не может иметь такой идентификатор), мьютекс не захвачен ни одним из потоков и находится в свободном состоянии.
  2. Если его идентификатор не равен 0, мьютекс захвачен одним из потоков и находится в занятом состоянии.
  3. Захват мьютекса осуществляется Wait-функцией, при этом значение идентификатор потока принимает значение захватившего его потока.
  4. Освободить мьютекс можно функциейReleaseMutex.
  5. В отличие от других объектов ядра, мьютекс можно захватить несколько раз одним и тем же потоком, при этом будет увеличиваться счётчик рекурсии.

ФункцияReleaseMutex- это функция освобождения мьютекса.

Семафоры (semaphor) — аналогичны мьютексам, но при их работе подсчитывается число обращений. Семафор – синхронизирующий объект ядра, который содержит счётчик числа пользователей и два 32-разрядных целых числа со знаком: счётчик текущего числа ресурсов от 0 и до значения максимального числа ресурсов (контролируемого семафором). Посредством семафора можно разрешить доступ к ресурсу, например, не более чем трем потокам одновременно. Мьютекс это тот же семафор с максимальным значением счетчика, равным 1. Создать семафор можно функциейCreateSemaphore, а открыть его  - функциейOpenSemaphore. При работе с семафорами необходимо следовать таким правилам:

  1. Если счётчик числа ресурсов равен нулю – семафор занят.
  2. Если счётчик числа ресурсов больше, чем нуль – семафор свободен.
  3. Счётчик числа ресурсов не может быть меньше нуля или больше, чем максимальное значение счётчика ресурсов.
  4. Каждое успешное ожидание семафора Wait-функцией уменьшает значение счётчика числа ресурсов на 1, т. е. поток захватывает ресурс.
  5. ФункцияReleaseSemaphore увеличивает значение счётчика числа пользователей на 1, может вызываться потоком, закончившим работать с ресурсом, или при поступлении клиентского запроса.
  6. Потоки, ожидающие доступ через семафор, получают его по принципу работы стека FIFO (First Input First Output) – первый поток, ставший на очередь ожидания получит доступ первым.

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

События (event) — могут применяться как средство синхронизации потока с системными событиями, такими как пользовательские операции с файлами. Метод WaitFor класса TThread (Delphi,C++Builder) использует событие. События могут также использоваться для того, чтобы активизировать одновременно несколько потоков. Событие – самая простая разновидность синхронизирующего объекта ядра, используется для уведомления об окончании какой-либо операции. Событие содержит счётчик числа пользователей (как и все объекты ядра) и две булевы переменные: одна сообщает тип данного объекта ядра «событие» (с авто-сбросом или без), другая – его состояние (свободен или занят).  Объект ядра «событие» бывает двух типов:

  • со сбросом вручную (manual-reset events) – вызов Wait-функции никак не влияет на состояние объект ядра «событие», что позволяет возобновить сразу несколько ждущих потоков и предоставить им доступ «только для чтения».
  • с авто-сбросом (auto-reset events) – при успешном ожидании Wait-функция автоматически переводит объект ядра «событие» в занятое состояние, что позволяет возобновить только один ждущий поток, он получит доступ, как для чтения, так и для записи.

Создать объект ядра «событие» можно при помощи функцииCreateEvent. В числе параметров этой функции есть два булевых параметра. Один параметр bManualReset определяет тип объекта ядра «событие». Если он равен TRUE, то объект ядра «событие»  – со сбросом вручную, если FALSE – с авто-сбросом. Параметр bInitialState определяет начальное состояние объекта ядра «событие». Если он равен TRUE, то объект ядра «событие» имеет «свободное» состояние, а если он равен FALSE – «занятое» состояние.

При помощи функцииOpenEvent можно открыть объект ядра «событие». Дляуправления состоянием объекта ядра «событие» можно использовать такие функци:SetEvent - переводит объект ядра «событие» в свободное состояние,ResetEvent - переводит объект ядра «событие» в занятое состояние. Далее в примере рассматривается использованиеобъекта ядра «событие» с авто-сбросом

Пример 8.2. Использование объекта ядра «событие» с авто-сбросом

HANDLEhMyEvent; // глобальный дескриптор события

intWINAPIWinMain(...)

{

  hMyEvent = CreateEvent( NULL, FALSE, FALSE, NULL);

  HANDLE hMyThread[2];

  DWORD dwThreadId;

  hMyThread[0] = _beginthreadex( NULL, ColorCorrector,   NULL, 0, &dwThreadId);

  hMyThread[1] = _beginthreadex( NULL, ContrastCorrector, NULL, 0, &dwThreadId);

LoadImage(...);

  // теперь один из потоков получит доступ к ресурсу (изображению)

SetEvent( hEvent );

  ...

}

//*************************************************************************

DWORD WINAPI ColorCorrection(LPVOID lpParam)

{

  WaitForSingleObject( hMyEvent, INFINITE );

  // событие автоматически переводится в занятое состояние

  // получен доступ к изображению и выполняем коррекцию цвета изображения

  SetEvent( hEvent ); // освобождаем объект ядра «событие»

return(0);

}

//*************************************************************************

DWORD WINAPI ContrastCorrector(LPVOID lpParam)

{ Потоки в операційній системі Windows. Засоби синхронізації потоків. Засоби роботи з потоками. Об’єкт TThread систем Delphi та C++ Builder для роботи з потоками на http://mirrorref.ru


Похожие рефераты, которые будут Вам интерестны.

1. Реферат Денежные потоки организации. Методы расчета. Подходы к управлению денежными потоками

2. Реферат Управління процесами і потоками в ОС Windows

3. Реферат Народознавство в системі навчально-виховної роботи школи

4. Реферат Технології роботи з файлам у OC Windows

5. Реферат Загальні правила роботи та безпеки праці під час роботи в протигазі

6. Реферат Лікарські засоби з групи вуглеводів. Глюкоза. Лікарські засоби з групи вітамінів

7. Реферат Організація роботи органів внутрішніх справ України щодо протидії дитячій злочинності: функції структурних підрозділів ОВС з профілактики дитячої злочинності; аналіз, облік та оцінка їх роботи

8. Реферат Потоки. Класи потоків С++

9. Реферат Управление денежными потоками

10. Реферат Системы управления материальными потоками на предприятии