Московский государственный технический университет им. Н.Э. Баумана
Опубликован: 28.06.2006 | Доступ: свободный | Студентов: 12459 / 340 | Оценка: 4.54 / 3.83 | Длительность: 22:03:00
ISBN: 978-5-9556-0055-0
Лекция 14:

Взаимодействие процессов и потоков

Атомарные операции

Атомарные операции обычно имеют более-менее близкое соответствие с командами процессора. Так, например, они могут сводиться к операциям с блокировкой шины (префикс lock ) и специальным командам (типа cmpxchg ) процессора. ОС Windows предоставляет функции для увеличения ( InterlockedIncrement, InterlockedIncrement64 ) или уменьшения ( InterlockedDecrement, InterlockedDecrement64 ) значения целочисленных переменных и изменения их значений ( InterlockedExchange, InterlockedExchange64, InterlockedExchangeAdd, InterlockedExchangePointer ), в том числе со сравнением ( InterlockedCompareExchange, InterlockedCompareExchangePointer ).

В приведенном выше примере было бы достаточно заменить оператор array[i]++ на вызов функции InterlockedIncrement:

unsigned _ _stdcall ThreadProc( void *param )
{
  int  i;
  for ( i = 0; i < ASIZE; i++ ) InterlockedIncrement(array+i);
  return 0;
}

Еще несколько функций предназначены для работы с односвязными LIFO списками. Функция InitializeSListHead подготавливает начальный указатель на LIFO список, функция InterlockedPushEntrySList добавляет новую запись в список, функция InterlockedPopEntrySList извлекает из списка последнюю добавленную запись и функция InterlockedFlushSList очищает список. Операции изменения указателей в списке являются атомарными.

Критические секции

Термин "критическая секция" обозначает некоторый фрагмент кода, который должен выполняться в исключительном режиме - никакие другие потоки и процессы не должны выполнять этот же фрагмент в то же время. В некоторых операционных системах для однопроцессорных машин вход в критическую секцию просто блокирует работу планировщика до выхода из секции. Этот подход не всегда эффективен: нахождение потока в критической секции не должно влиять на возможность исполнения кода, не принадлежащего именно данной критической секции, другими потоками, особенно на многопроцессорных машинах.

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

Существуют четыре основных функции для работы с критическими секциями; перед использованием критическая секция должна быть инициализирована с помощью функции InitializeCriticalSection. Объект CRITICAL_SECTION, принадлежащий пользовательскому процессу, может использовать в своей реализации объекты ядра для ожидания; эти объекты могут создаваться по мере надобности, и для окончательного освобождения ресурсов, после использования критической секции она должна быть удалена с помощью функции DeleteCriticalSection.

Собственно работа с критическими секциями сводится к двум основным функциям: функция EnterCriticalSection, которая соответствует входу в критическую секцию, при необходимости с ожиданием, не ограниченным по времени (!); и функция LeaveCriticalSection, которая соответствует выходу из этой секции, возможно с пробуждением потоков, ожидающих ее освобождения. При этом система исключает вход в критическую секцию всех остальных потоков процесса, в то время как поток, уже вошедший в данную секцию, может входить в нее рекурсивно - надо лишь, чтобы число выходов из секции соответствовало числу входов:

#include <stdio.h>
#include <process.h>
#define _WIN32_WINNT 0x0403
#include <windows.h>

#define THREADS 		10
#define ASIZE 			10000000
static  LONG		        array[ASIZE];
static  CRITICAL_SECTION 	CS;

unsigned _ _stdcall ThreadProc( void *param )
{
  int  i;
  for ( i = 0; i < ASIZE; i++ ) {
    EnterCriticalSection( &CS );
    array[i]++;
    LeaveCriticalSection( &CS );
  }
  return 0;
}

int main( void )
{
  HANDLE  	hThread[THREADS];
  unsigned 	dwThread;
  int    		i, errs;
  InitializeCriticalSectionAndSpinCount( &CS, 100 );
  for ( i = 0; i < THREADS; i++ )
    hThread[i] = (HANDLE)_beginthreadex(
      NULL, 0, ThreadProc, (void*)i, 0, &dwThread );
  WaitForMultipleObjects( THREADS, hThread, TRUE, INFINITE );
  for ( i = 0; i < THREADS; i++ ) CloseHandle( hThread[i] );
  for ( errs=i=0; i<ASIZE; i++ )
    if ( array[i] != THREADS ) errs++;
  if ( errs ) printf("Detected %d errors!\n", errs );
  DeleteCriticalSection( &CS );
  return 0;
}

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

Кроме того, эффективность критических секций на многопроцессорных машинах может быть повышена, если перед началом ожидания занятой секции, вместо перехода к ожиданию в режиме ядра, выполнить предварительный кратковременный цикл с опросом состояния секции. Если секция занимается другим потоком на небольшое время, то такой цикл позволяет дождаться ее освобождения, не переходя в режим ядра. Для этого предназначены функции: InitializeCriticalSectionAndSpinCount и SetCriticalSectionSpinCount. Они позволяют задавать число опросов состояния занятой критической секции перед переходом в режим ядра для ожидания. На однопроцессорных машинах опросы не выполняются и функция InitializeCriticalSectionAndSpinCount ничем не отличается от обычной функции InitializeCriticalSection.

Анастасия Булинкова
Анастасия Булинкова
Рабочим названием платформы .NET было