
- •Операционные системы Windows32
- •Объекты ядра Windows
- •Пользователи объектов ядра
- •Защита объектов ядра
- •Дескрипторы. Таблица дескрипторов объектов ядра.
- •Создание объектов ядра
- •Закрытие объектов ядра
- •Совместное использование объектов ядра различными процессами
- •Процессы и потоки в Windows Создание процесса
- •Завершение процесса
- •Операции с процессами
- •Создание потока
- •Завершение потока
- •Потоки и библиотека языка с
- •Операции с потоками
- •Приоритеты потоков в Windows
- •Потоки в мультипроцессорных системах
- •Синхронизация потоков
- •Interlocked-функции
- •Критические секции
- •Wait-функции
- •Wait-функции для работы с одним объектом
- •Wait-функции для работы с несколькими объектами.
- •Побочные эффекты ожидания.
- •События
- •Семафоры
- •Мьютексы
- •Ожидаемые таймеры
- •Управление памятью в Windows
- •Организация виртуальной памяти Windows
- •Выделение памяти процессу
- •Атрибуты защиты страниц памяти
- •Функции менеджера памяти Windows
- •Проецируемые в память файлы
- •Проекции файлов и разделяемая память
Приоритеты потоков в Windows
Описать работу с приоритетами.
Потоки в мультипроцессорных системах
Описать работу с процессорами.
Синхронизация потоков
Windows предоставляет целый ряд средств для синхронизации между потоками и процессами в системе. Мы рассмотрим следующие средства:
Interlocked-функции позволяют выполнять некоторые простые действия с переменными атомарно, и таким образом, могут использоваться для синхронизации выполнения потоков одного процесса с минимальными издержками;
критические секции предназначены для организации эксклюзивного доступа к участкам кода или данным внутри одного процесса;
объекты синхронизации ядра Windows и специальные Wait-функции могут быть использованы как для синхронизации как между потоками одного процесса, так и между процессами.
Interlocked-функции
Наиболее простой способ синхронизации доступа к переменным обеспечивают так называемые Interlocked-функции. Эти функции обеспечивают выполнение некоторых простых операций в атомарном режиме (т.е. они не могут быть прерваны в процессе другими потоками). Рассмотрим основные функции этой группы.
LONG InterlockedExchange(
LPLONG Target, // value to exchange
LONG Value // new value
);
Эта функция заменяет значение переменной, на которую указывает параметр Target на значение Value и возвращает предыдущее значение.
LONG InterlockedCompareExchange(
LPLONG Destination, // destination address
LONG Exchange, // exchange value
LONG Comperand // value to compare
);
InterlockedCompareExchange выполняет сравнение значения переменной, на которую указывает параметр Destination со значение Comperand. Если эти значения совпадают, то значение Destination заменяется на значение Exchange. Если значения не совпадают, то никаких действий не предпринимается. Функция возвращает исходное значение Destination.
LONG InterlockedDecrement(
LPLONG lpAddend // variable address
);
Функция уменьшает значение переменной, на которую указывает lpAddend и возвращает полученное значение. В старых версиях Windows (Windows 95, NT 3.51 и ранее) возвращалось значение, которое по знаку совпадало с результатом операции, но могло не совпадать по модулю.
LONG InterlockedExchangeAdd (
LPLONG Addend, // addend
LONG Value // increment value
);
InterlockedExchangeAdd увеличивает значение Addend на Value и возвращает исходное значение Addend.
LONG InterlockedIncrement(
LPLONG lpAddend // variable to increment
);
Функция увеличивает значение lpAddend на 1 и возвращает полученное значение. Как и в случае функции InterlockedDecrement в Windows95, NT 3.51 и более ранние версии возвращали значение, которое совпадает с результатом только по знаку.
Критические секции
Во многих случаях при разработке многопоточных приложений возникает задача организации монопольного доступа к какому-либо ресурсу. Например, нам необходимо добавить запись в буфер и увеличить счетчик занятых элементов, причем эту операцию могут выполнять несколько потоков одновременно. Изменение буфера и счетчика это достаточно сложная и длинная операция, и выполнить ее атомарно с помощью Interlocked-функций мы не можем, а неатомарное выполнение может привести к повреждению данных при одновременной работе. Решить эту проблему может механизм, называемый критическая секция.
Для использования критической секции мы должны, прежде всего, для каждого ресурса с монопольным доступом создать переменную типа CRITICAL_SECTION. Эта переменная должна быть инициализирована с помощью функции InitializeCriticalSection:
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
В качестве параметра этой функции передается указатель на наш объект.
Перед началом работы с ресурсом каждый поток должен вызвать функцию EnterCriticalSection:
VOID EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
Завершив работу с ресурсом необходимо вызвать функцию LeaveCriticalSection:
VOID LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
Объект критическая секция запоминает, какой поток захватил его, вызвав EnterCriticalSection. Если после этого другой поток попытается захватить тот же объект, то вызванная функция EnterCriticalSection увидит, что объект занят и переведет поток в состояние ожидания до тех пор, пока критическая секция не освободится. Вызвав функцию LeaveCriticalSection, поток освобождает критическую секцию, давая, тем самым, возможность выполнится (или захватить ее позднее) одному из ожидающих потоков.
Если поток, который уже захватил критическую секцию, попытается захватить её еще раз, то эта операция будет успешно выполнена. Однако в этом случае ему придется и освобождать эту критическую секцию столько же раз, сколько он ее захватывал, чтобы она вновь стала свободной для других потоков.
Следует соблюдать осторожность при работе с несколькими критическими секциями, чтобы не создать возможность возникновения deadlock’ов. Рассмотрим два потока, которые захватывают критические секции A и B в разном порядке:
-
Поток 1
Поток 2
EnterCriticalSection(&A);
EnterCriticalSection(&B);
…
EnterCriticalSection(&B);
EnterCriticalSection(&A);
…
Легко видеть, что при таком порядке выполнения потоки блокируют друг друга. Для того, чтобы избежать такой ситуации лучше всего постараться использовать вместо двух критических секций одну. Если это не удается, нужно чтобы во всех случаях использования нескольких критических секций они захватывались всегда в одном порядке (например всегда сначала А, потом В).
Когда критическая секция нам будет больше не нужна, нужно освободить используемые ей системные ресурсы, вызвав функцию DeleteCriticalSection:
VOID DeleteCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
Таким образом, обычный порядок работы с критической секцией выглядит следующим образом:
// создаем переменную - критическую секцию
CRITICAL_SECTION cs;
void main(void)
{
// перед началом работы инициализируем критическую секцию
InitializeCriticalSection(&cs);
// работа программы
// в конце освобождаем ресурсы
DeleteCriticalSection(&cs);
}
…
DWORD WINAPI SomeThreadProc(LPVOID Param)
{
…
// в функции потока используем критическую секцию
EnterCriticalSection(&cs);
// работаем с защищенным ресурсом в монопольном режиме
// освобождаем критическую секцию
LeaveCriticalSection(&cs);
…
}
Основной проблемой использования функции EnterCriticalSection является то, что ей нельзя указать время ожидания, и она может заблокировать поток на бесконечно долгое время. В Windows не предусмотрено функции, которая бы позволяла ожидать освобождения критической секции установленное время, однако есть функция, которая позволяет проверить, свободна или занята критическая секция. Это функция TryEnterCriticalSection:
BOOL TryEnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
Если критическая секция занята, то функция немедленно вернет значение FALSE. Если же критическая секция свободна, то TryEnterCriticalSection захватывает критическую секцию и возвращает TRUE (в этом случае можно сразу начинать использовать защищенный ресурс, вызвать EnterCriticalSection не надо).