Студопедия

КАТЕГОРИИ:

АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника


Синхронизация потоков в пределах одного процесса. Критические секции. Спин-блокировки. Interlocked-функции.




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

Ø атомарные операции API-уровня;

Ø критические секции;

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

CRITICAL_SECTION cs;

Поскольку эта переменная должна находиться в области видимости для каждого использующего ее потока, обычно она объявляется как глобальная. Эту переменную следует инициализировать до ее первого применения с помощью функции InitializeCriticalSection(&cs); Чтобы завладеть критическим участком, поток должен вызвать функцию EnterCriticalSection(&cs);

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

Конец критического участка обозначается вызовом функции LeaveCriticalSection(&cs);

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

Спин-блокировки представляют собой чрезвычайно низкоуровневое средство синхронизации, предназначенное в первую очередь для применения в многопроцессорной конфигурации с разделяемой памятью. Они обычно реализуются как атомарно устанавливаемое булево значение (истина – блокировка установлена). Аппаратура поддерживает подобные блокировки командами вида "проверить и установить".

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

Большая часть синхронизации потоков связана с атомарным доступом (atomic access) — монопольным захватом ресурса обращающимся к нему потоком. Win32 API предоставляет несколько функций для реализации взаимно блокированных операций. Все Interlocked-функций работают корректно только при условии, что их аргументы выровнены по границе двойного слова (DWORD).

Функция Interlockedlncrement, имеющая прототип LONG InterlockedIncrement(LPLONG lpAddend); инкрементирует 32-разрядную переменную, адрес которой задается параметром LpAddend. Функция возвращает новое значение указанной переменной.

Функция Interlocked Decrement определена аналогично функции Interlockedlncrement, но она декрементирует 32-разрядную переменную. Пара функций

LONG InterlockedExchange(LPLONG IpTarget. LONG Value):

PVOID InterlockedExchangePointer(PVOID* ppvTarget. PVOID pvValue):

монопольно заменяет текущее значение переменной типа LONG, адрес которой передается в первом параметре, значением, передаваемым во втором параметре. В 32-разрядном приложении обе функции работают с 32-разрядными значениями. В 64-разрядной программе первая функция оперирует 32-разрядными значениями, а вторая — 64-разрядными. Обе функции возвращают исходное значение переменной.

Следующая функция добавляет к значению переменной, адрес которой передается в первом параметре, значение, передаваемое во втором параметре:

LONG InterlockedExchangeAdd(LPLONG IpAddend. LONG Increment):

Еще две функции выполняют операцию сравнения и присваивания по результату сравнения:

LONG InterlockedCompareExchangetLPLONG IpDestination, LONG Exchange, LONG Comparand);

PVOID InterlockedCompareExchangePointer(PVOID* ppvDestination, PVOID pvExchange, PVOID pvComparand);

Если значение переменной, адрес которой передается в первом параметре, совпадает со значением, передаваемым в третьем параметре, то оно заменяется значением, передаваемым во втором параметре. В 32-разрядном приложении обе функции работают с 32-разрядными значениями. В 64-разрядной программе первая функция оперирует 32-разрядными значениями, а вторая — 64-разрядными. Обе функции возвращают исходное значение переменной, заданной первым параметром.

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

 

20. Синхронизация потоков разных процессов. Объекты синхронизации: флаги, семафоры, события, ожидаемые таймеры, именованные и неименованные «трубы» (каналы).

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

Событие (event) — самая простая разновидность объектов ядра. Оно содержит счетчик количества пользователей и две булевы переменные. Одна переменная указывает тип данного объекта-события, а другая — его состояние. События просто уведомляют об окончании какой-либо операции. Объекты-события бывают двух типов: со сбросом вручную (manual-reset events) или с автосбросом (auto-reset events). Первые события позволяют возобновить выполнение сразу нескольких ждущих потоков, а вторые — только одного потока.

Объект ядра «событие» создается функцией CreateEvent, имеющей следующий

прототип:

HANDLE CreateEvent(

LPSECURITY_ATTRIBUTES eventAttributes, // атрибуты доступа

BOOL bManualReset, // тип сброса

BOOL blnitialState, // начальное состояние

LPCTSTR pszName // имя объекта

):

Объекты ядра «семафор» (semaphore) используются для учета ресурсов. Кроме счетчика числа пользователей семафор содержит два 32-битных значения со знаком. Одно из них определяет максимальное количество ресурсов, контролируемых семафором, а другое используется как счетчик текущего числа ресурсов. Объект ядра «семафор» создается вызовом функции CreateSemaphore:

HANDLE CreateSemaphore (

LPSECURITY_ATTRIBUTES semaphoreAttributes. // атрибуты доступа

LONG llnitialCount, // текущее количество доступных ресурсов

LONG IMaximumCount, // максимальное количество ресурсов

LPCTSTR pszName // имя объекта

);

Например, в результате следующего вызова: HANDLE hSem = CreateSemaphore(NULL, 0. 5, "MySemaphore"); будет создан именованный объект-семафор с максимальным числом ресурсов, равным пяти, и изначально нулевым количеством доступных ресурсов.

Поток может увеличить значение счетчика текущего числа доступных ресурсов на величину LReleaseCount, вызывая функцию ReleaseSemaphore:

BOOL ReleaseSemaphoreC

HANDLE hSemaphore. // дескриптор семафора

LONG IReleaseCount. // приращение количества доступных ресурсов

LPLONG lpPreviousCount // предыдущее значение счетчика ресурсов

):

Пожалуй, ожидаемые таймеры - самый изощренный объект ядра для синхронизации. Таймеры создаются функцией CreateWaitableTimer и бывают, также как и события, с автосбросом и без него. Затем таймер надо настроить функцией SetWaitableTimer. Таймер переходит в сигнальное состояние, когда истекает его таймаут. Отменить "тиканье" таймера можно функцией CancelWaitableTimer. Примечательно, что можно указать callback функцию при установке таймера. Она будет выполняться, когда срабатывает таймер.

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

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

Работает канал по схеме: первым вошел – первым вышел, то есть, та информация, которая была записана в канал раньше, будет раньше считана. Это, так называемая, процедура FIFO (First In – First Out). Канал рассчитан на определенный объем информации (обычно 4096 байт). Если канал заполнен и никто из него не читает, процесс записи приостанавливается. Если информация из канала считана и в него никто не пишет, приостанавливается процесс чтения.

 


Поделиться:

Дата добавления: 2015-01-29; просмотров: 122; Мы поможем в написании вашей работы!; Нарушение авторских прав





lektsii.com - Лекции.Ком - 2014-2024 год. (0.005 сек.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав
Главная страница Случайная страница Контакты