Введення, C / C + +, Програмування, статті

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

Невелике зауваження: функція _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>

*

*