Программирование для Windows NT

Блокирующие функции


Если несколько задач выполняют изменение одной и той же глобальной переменной, их необходимо синхронизировать, чтобы одновременно к этой переменной обращалась только одна задача. Для этого вы можете воспользоваться рассмотренными нами ранее критическими секциями или объектами Mutex, однако в программном интерфейсе операционной системы Microsoft Windows NT предусмотрено несколько функций, которые просты в использовании и могут оказаться полезными в данной ситуации.

Функции InterlockedIncrement и InterlockedDecrement выполняют, соответственно, увеличение и уменьшение на единицу значения переменной типа LONG, адрес которой передается им в качестве единственного параметра:

LONG InterlockedIncrement(LPLONG lpAddend);

LONG InterlockedDecrement(LPLONG lpAddend);

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

В результате при использовании этих функций можно быть уверенным, что изменение переменной будет выполнено правильно.

А как может произойти ошибка?

Пусть, например, первая задача увеличивает содержимое глобальной переменной lAddend следующим образом:

lAddend += 1;

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

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


Очевидно, что операция присваивания глобальной переменной нового значения, если она выполняется несколькими задачами одновременно, таит в себе ту же опасность. Например, следующая строка исходного текста может быть транслирована в несколько машинных команд и, следовательно, может быть прервана другой задачей того же процесса:

lTaget = lNewValue;

Специально для того чтобы избежать такой опасности, в программном интерфейсе Microsoft Windows NT предусмотрена функция InterlockedExchange:

LONG InterlockedExchange(

  LPLONG lpTarget,   // адрес изменяемой переменной

  LONG   lNewValue); // новое значение для переменной

Эта функция записывает значение lNewValue по адресу lpTarget. При этом гарантируется, что операция не будет прервана другой задачей, выполняющейся в рамках того же процесса.

   Функция InterlockedExchange возвращает старое значение изменяемой переменной.

Что же касается значения, возвращаемого функциями InterlockedIncrement и InterlockedDecrement, то оно равно нулю, если в результате изменений значение переменной стало равно нулю. Если в результате увеличения или уменьшения значение переменной стало больше или меньше нуля, то эти функции возвращают, соответственно, значение, большее или меньшее нуля. Это значение, однако, можно использовать только для сравнения, так как абсолютная величина возвращенного значения не равна новому значению изменяемой переменной.


Содержание раздела