Введення

У статті розглядаються методи синхронізації потоків одного або кількох
процесів. Всі методи засновані на створенні спеціальних об'єктів синхронізації.
Ці об'єкти характеризуються станом. Розрізняють сигнальне та несігнальное
стан. Залежно від стану об'єкта синхронізації один потік може
дізнатися про зміну стану інших потоків або загальних (поділюваних) ресурсів.

Невелике зауваження: функція _beginthread, використовувана в прикладах, може
бути замінена відповідним еквівалентом MFC (AfxBeginThread) або аналогічної
в інших діалектах мови С.


Несинхронізовані потоки


Перший приклад ілюструє роботу з несинхронізованих потоками. Основний
цикл, який є основним потоком процесу, виводить на екран вміст
глобального масиву цілих чисел. Потік, названий "Thread", безперервно заповнює
глобальний масив цілих чисел.

  #include <process.h>
#include <stdio.h>

int a[ 5 ];

void Thread( void* pParams )
{ int i, num = 0;

while ( 1 )
{
for ( i = 0; i < 5; i++ ) a[ i ] = num;
num++;
}
}

int main( void )
{
_beginthread( Thread, 0, NULL );

while( 1 )
printf("%d %d %d %d %d
",
a[ 0 ], a[ 1 ], a[ 2 ],
a[ 3 ], a[ 4 ] );

return 0;
}


Як видно з результату роботи процесу, основний потік (сама програма) і
потік Thread дійсно працюють паралельно (червоним кольором позначено
стан, коли основний потік виводить масив під час його заповнення потоком
Thread):


81751652 81751652 81751651 81751651
81751651
81751652 81751652 81751651 81751651
81751651
83348630 83348630 83348630 83348629
83348629
83348630 83348630 83348630 83348629
83348629
83348630 83348630 83348630 83348629
83348629

Запустіть програму, потім натисніть "Pause" для зупинки виводу на дисплей
(Тобто припиняються операції введення / виводу основного потоку, але потік Thread
продовжує своє виконання у фоновому режимі) і будь-яку іншу клавішу для
відновлення виконання.

Критичні секції


А що робити, якщо основний потік повинен читати дані з масиву після його
обробки в паралельному процесі? Одне з рішень цієї проблеми – використання
критичних секцій.


Критичні секції забезпечують синхронізацію подібно м'ютексом (про
мьютекса див. далі) за винятком того, що об'єкти, що представляють
критичні секції, доступні в межах одного процесу. Події, м'ютекси і
семафори також можна використовувати в "однопроцессном" додатку, проте
критичні секції забезпечують більш швидкий і більш ефективний механізм
взаємно-виключає синхронізації. Подібно м'ютексом об'єкт, що представляє
критичну секцію, може використовуватися тільки одним потоком в даний момент
часу, що робить їх вкрай корисними при розмежуванні доступу до загальних
ресурсів. Важко припустити що-небудь про порядок, в якому потоки будуть
отримувати доступ до ресурсу, можна сказати лише, що система буде "справедлива"
до всіх потоків.

  #include <windows.h>
#include <process.h>
#include <stdio.h>

CRITICAL_SECTION cs;
int a[ 5 ];

void Thread( void* pParams )
{
int i, num = 0;

while ( TRUE )
{
EnterCriticalSection( &cs );
for ( i = 0; i < 5; i++ ) a[ i ] = num;
LeaveCriticalSection( &cs );
num++;
}
}

int main( void )

{
InitializeCriticalSection( &cs );
_beginthread( Thread, 0, NULL );

while( TRUE )
{
EnterCriticalSection( &cs );
printf( "%d %d %d %d %d
",
a[ 0 ], a[ 1 ], a[ 2 ],
a[ 3 ], a[ 4 ] );
LeaveCriticalSection( &cs );
}
return 0;
}


М'ютекси (взаємовиключення)


М'ютекс (взаємовиключає, mutex) – це об'єкт синхронізації, який
встановлюється в особливе сигнальний стан, коли не зайнятий яким-небудь
потоком. Тільки один потік володіє цим об'єктом у будь-який момент часу, звідси
і назва таких об'єктів – одночасний доступ до загального ресурсу виключається.
Наприклад, щоб виключити запис двох потоків до загального ділянку пам'яті в один і той
Водночас, кожен потік очікує, коли звільниться мьютекс, стає його
власником і лише потім пише що-небудь в цю ділянку пам'яті. Після всіх
необхідних дій м'ютекс звільняється, надаючи іншим потокам доступ до
загального ресурсу.

Два (чи більше) процесу можуть створити мьютекс з одним і тим же ім'ям,
викликавши метод CreateMutex. Перший процес дійсно створює
мьютекс, а наступні процеси отримують Хендлі вже існуючого об'єкта. Це дає
можливість декільком процесам отримати Хендлі одного і того ж мьютекса,
звільняючи програміста від необхідності піклуватися про те, хто в
дійсності створює мьютекс. Якщо використовується такий підхід, бажано
встановити прапор bInitialOwner в FALSE, інакше виникнуть певні
труднощі при визначенні дійсного творця мьютекса.

Кілька процесів можуть отримати Хендлі одного і того ж мьютекса, що
робить можливим взаємодія між процесами. Ви можете використовувати
наступні механізми такого підходу:


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

  #include <windows.h>
#include <process.h>
#include <stdio.h>

HANDLE hMutex;
int a[ 5 ];

void Thread( void* pParams )
{
int i, num = 0;

while ( TRUE )
{
WaitForSingleObject( hMutex, INFINITE );
for ( i = 0; i < 5; i++ ) a[ i ] = num;
ReleaseMutex( hMutex );
num++;
}
}

int main( void )
{
hMutex = CreateMutex( NULL, FALSE, NULL );
_beginthread( Thread, 0, NULL );

while( TRUE )

{
WaitForSingleObject( hMutex, INFINITE );
printf( "%d %d %d %d %d
",
a[ 0 ], a[ 1 ], a[ 2 ],
a[ 3 ], a[ 4 ] );
ReleaseMutex( hMutex );
}
return 0;
}


Події


А що, якщо ми хочемо, щоб у попередньому прикладі другий потік запускався
кожного разу після того, як основний потік закінчить друк вмісту масиву,
тобто значення двох наступних рядків будуть відрізнятися строго на 1?


Подія – це об'єкт синхронізації, стан якого може бути встановлено
в сигнальний шляхом виклику функцій SetEvent або
PulseEvent. Існує два типи подій:













Тип об'єкта Опис
Подія з ручним скиданням Це об'єкт, сигнальний стан якого зберігається до ручного
скидання функцією ResetEvent. Як тільки стан об'єкта
встановлено в сигнальний, що все знаходяться в циклі очікування цього об'єкта потоки
продовжують своє виконання (звільняються).
Подія з автоматичним скиданням Об'єкт, сигнальний стан якого зберігається до тих пір, поки не буде
звільнений єдиний потік, після чого система автоматично встановлює
несігнальное стан події. Якщо немає потоків, які очікують цієї події,
об'єкт залишається в сигнальному стані.

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


Потік може використовувати функцію CreateEvent для створення
об'єкта події. Створює подія потік встановлює його початковий стан.
У створює потоці можна вказати назву події. Потоки інших процесів можуть
отримати доступ до цієї події по імені, вказавши його у функції
OpenEvent.


Потік може використовувати функцію PulseEvent для установки
стану події в сигнальний і потім скинути стан в несігнальное після
звільнення відповідної кількості очікують потоків. У випадку об'єктів з
ручним скиданням звільняються всі очікують потоки. У випадку об'єктів з
автоматичним скиданням звільняється тільки єдиний потік, навіть якщо цього
події очікують кілька потоків. Якщо очікують потоків немає,
PulseEvent просто встановлює стан події в
несігнальное.

  #include <windows.h>
#include <process.h>
#include <stdio.h>

HANDLE hEvent1, hEvent2;
int a[ 5 ];

void Thread( void* pParams )
{
int i, num = 0;

while ( TRUE )
{
WaitForSingleObject( hEvent2, INFINITE );
for ( i = 0; i < 5; i++ ) a[ i ] = num;
SetEvent( hEvent1 );
num++;
}
}

int main( void )
{
hEvent1 = CreateEvent( NULL, FALSE, TRUE, NULL );
hEvent2 = CreateEvent( NULL, FALSE, FALSE, NULL );

_beginthread( Thread, 0, NULL );

while( TRUE )
{
WaitForSingleObject( hEvent1, INFINITE );
printf( "%d %d %d %d %d
",
a[ 0 ], a[ 1 ], a[ 2 ],
a[ 3 ], a[ 4 ] );
SetEvent( hEvent2 );
}
return 0;
}


Порівняння об'єктів синхронізації


У MSDN News за липень / серпень 1998р. є стаття про об'єкти синхронізації.
Наступна таблиця взята з цієї статті:


































Об'єкт Відносна швидкість Доступ декількох процесів Підрахунок числа звернень до ресурсу Платформи
Критична секція швидко Ні Ні (ексклюзивний доступ) 9x/NT/CE
М'ютекс повільно Так Ні (ексклюзивний доступ) 9x/NT/CE
Семафор повільно Так Автоматично 9x/NT
Подія повільно Так Так 9x/NT/CE

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*