Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторная работа Потоки.doc
Скачиваний:
9
Добавлен:
19.12.2018
Размер:
178.69 Кб
Скачать

Void LeaveCriticalSection(pcritical_section pcs);

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

Далее приведен пример использования потоков.

На рисунке отображены параллельно выполняющиеся потоки: «Поток 1» и «Поток 2». Им обоим необходим доступ к разделяемому ресурсу, скажем к файлу. Причем одному потоку необходим доступ на чтение, а второму доступ на запись. Разделяемый ресурс защищен критической секцией. На рисунке видно, что первый поток быстрее захватит критическую секцию, второй поток будет ожидать до тех пор, пока первый не осуществит доступ к ресурсу и не освободит секцию, тогда второй поток в свою очередь захватит секцию, и первый поток при попытке захватить ее будет ожидать.

9.2. Правила использования критических секций

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

На каждый разделяемый ресурс следует использовать отдельную структуру CRITICAL_SECTION.

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

Посмотрите на этот фрагмент кода:

int g_nNums[100]; // один разделяемый ресурс

TCHAR g_cChars[100]; // Другой разделяемый ресурс

CRITICAL_SECTION g_cs, // защищает оба ресурса

DWORD WINAPI ThreadFunc(PVOID pvParam) {

EnterCriticalSection(&g_cs);

for (int x = 0; x < 100: x++) {

g_nNums[x] = 0; g_cChars|x] = TEXT('X');

}

LeaveCriticalSection(&g_cs); return(0); }

Здесь создана единственная критическая секция, защищающая оба массива — g_nNums и g_cChars — в период их инициализации. Но эти массивы совершенно различны. И при выполнении данного цикла ни один из потоков не получит доступ ни к одному массиву. Теперь посмотрим, что будет, если ThreadFunc реализовать так:

DWORD WINAPI ThreadFunc(PVOID pvParam) {

EnterCriticalSection(&g_cs); for (int x = 0; x < 100; x++)

g_nNums[x] = 0; for (x = 0; x < 100; x++)

g_cChars[x] = TEXT('X'); LeaveCriticalSection(&g_cs); return(0);

}

В этом фрагменте массивы инициализируются по отдельности, и теоретически после инициализации g_nNums посторонний поток, которому нужен доступ только к первому массиву, сможет начать исполнение — пока ThreadFunc занимается вторым массивом. Увы, это невозможно: обе структуры данных защищены одной критической секцией. Чтобы выйти из затруднения, создадим две критические секции:

int g_nNum[100]; // разделяемый ресурс

CRITICAL_SECTION g_csNums; // защищает g_nNums

TCHAR g_cChars[100]; // другой разделяемый ресурс

CRITICAL_SECTION g_csChars; // защищает g_cChars

DWORD WTNAPT ThreadFunc(PVOTD pvParam) {

EnterCriticalSection(&g_csNums); for (int x = 0; x < 100; x++) g_nNums[x] = 0; LeaveCriticalSection(&g_csNums); EnterCriticalSection(&g_csChars); for (x = 0; x < 100; x++) g_cChars[x] = TEXT('X'); LeaveCriticalSection(&g_csChars); return(0);

}

Теперь другой поток сможет работать с массивом g_nNums, как только ThreadFunc закончит его инициализацию. Можно сделать и так, чтобы один поток инициализировал массив g_nNums, другой — g_csChars.

Одновременный доступ к нескольким ресурсам.

Иногда нужен одновременный доступ сразу к двум структурам данных. Тогда ThreadFunc можно реализовать так:

DWORD WINAPI ThreadFunc(PVOID pvParam) {

EnterCriticalSection(&g_csNums);

EnterCriticalSection(&g_csChars);

// в этом цикле нужен одновременный доступ к обоим ресурсам

for (int x = 0; x < 100; x++) g_nNums[x] = g_cChars[x];

LeaveCriticalSection(&g_csChars);

LeaveCrilicalSection(&g_csNums};

return(0); }

Предположим, доступ к обоим массивам требуется и другому потоку в данном процессе; при этом его функция написана следующим образом:

DWORD WINAPI OtherThreadFunc(PVOID pvParam) {

EnterCriticalSection(&g_csChars); EnterCriticalSection(&g_csNums);

for (int x = 0; x < 100; x++) g_nNums[x] = g_cChars[x]; LeaveCriticalSection(&g_csNums); LeaveCriticalSection(&g_csChars); return(0);

}

Изменен порядок вызовов EnterCriticalSection и LeaveCriticalSection. Но из за того, что функции ThreadFunc и OtherThreadFunc написаны именно так, существует вероятность взаимной блокировки (deadlock). Допустим, ThreadFunc начинает исполнение и занимает критическую секцию g_nNums. Получив от системы процессорное время, поток с функцией OtherThreadFunc захватывает критическую секцию g_cChars. Тут-то и происходит взаимная блокировка потоков. Какая бы из функций — ThreadFunc или OtherThreadFunc — ни пыталась продолжить исполнение, она не сумеет занять другую, необходимую ей критическую секцию. Эту ситуацию легко исправить, написав код обеих функций так, чтобы они вызывали EnterCriticalSection в одинаковом порядке. Заметьте, что порядок вызовов LeaveCrititalSection несуществен, поскольку эта функция никогда не приостанавливает поток.

Не следует занимать критические секции надолго.

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

SOMESTRUCT g_s; CRITICAL_SECTION g_cs;

DWORD WINAPI SomeThread(PVOID pvParam) {

EnterCriticalSection(&g_cs);

// посылаем в окно сообщение SendMessage(hwndSomeWnd, WM_SOMEMSG, &g_s, 0); LeaveCriticalSection(&g_cs); return(0);

}

Трудно сказать, сколько времени уйдет на обработку WM_SOMEMSG оконной процедурой — может, несколько миллисекунд, а может, и несколько лет. В течение этого времени никакой другой поток не получит доступ к структуре g_сs. Поэтому лучше составить код иначе

SOMESTRUCT g_s; CRITICAL_SECTION g_cs;

DWORO WINAPI SomeThread(PVOID pvParam) {

EnterCriticalSection(&g_cs); SOMESTRUCT sTemp = g_s; LeaveCriticalSection(&g_cs);

// посылаем в окно сообщение SendMessage(hwndSompWnd, WM_SOMEMSG, &sTemp, 0); return(0);

}

Этот код сохраняет значение элемента g_s, во временной переменной sTemp. Нетрудно догадаться, что на исполнение этой строки уходит всего несколько тактов процессора. Далее программа сразу вызывает LeaveCriticalSection — защищать глобальную структуру больше не нужно. Так что вторая версия программы намного лучше первой, поскольку другие потоки «отлучаются» от структуры g_s лишь на несколько тактов процессора, а не на неопределенно долгое время. Такой подход предполагает, что «моментальный снимок» структуры вполне пригоден для чтения оконной процедурой, а также что оконная процедура не будет изменять элементы этой структуры.

Замечание:

Для использования в VC 6.0. многопоточной библиотеки необходимо включить ее Settings – C/C++ -Category: Code Generation – Use run-time library: Debug Multithreaded или Multithreaded, а также подключить файл process.h.