Побочные эффекты успешного ожидания
Успешный вызов WaitForSingleObject или WaitForMultipleObjecls на самом деле меняет состояние некоторых объектов ядра. Под успешным вызовом я имею в виду тот, при котором функция видит, что объект освободился, и возвращает значение, относитель ное WAITOBJECT_0. Вызов считается неудачным, если возвращается WAIT_TIMEOUT или WAIT_FAILED. В последнем случае состояние каких-либо объектов не меняется.
Изменение состояния объекта в результате вызова я называю побочным эффек, том успешного ожидания (successful wait side effect). Например, поток ждет объест «событие с автосбросом" (auto-reset event objcct) (об этих объектах я расскажу чуть позжс) Когда объект переходит в свободное состояние, функция обнаруживает это и может вернуть вызывающему потоку значение WAIT_OBJECT_0. Однако перед самым возвратом из функции событие переводится в занятое состояние — здесь сказывает ся побочный эффект успешного ожидания.
Объекты ядра «событие с автосбросом» ведут себя подобным образом, потому что таково одно из правил, определенных Microsoft для объектов этого типа. Другие объек ты дают иные побочные зффекты, а некоторые — вообще никаких К последним от носятся объекты ядра «процесс" и «поток", так что поток, ожидающий один из этих объектов, никогда не изменит его состояние. Подробнее о том, как ведут себя объек ты ядра, я буду рассказывать при рассмотрении соответствующих объектов.
Чем ценна функция WaitForMultipleObjects, так это тем, что она выполняет все дей ствия на уровне атомарного доступа. Когда поток обращается к этой функции, она ждет освобождения вссх объектов и в случае успеха вызывает в них требуемые по бочные эффекты; причем все действия выполняются как одна операция
Возьмем такой пример. Два потока вызывают WaitForMultipleObjects совершенно одинаково.
HANDLE h[2];
h[0] = hAutoResetEvent1;
// изначально занят
h[1] = hAutoResetEvent2;
// изначально занят
WaitForMulUpleObjects(2, И, TRUE, INFINITE);
На момент вызова WaitForMultipleObjects эти объекты-события заняты, и оба пото ка переходят в режим ожидания Но вот освобождается объект hAutoResetEventl Это становится известным обоим потокам, однако ни один из них не пробуждается, так как объект hAutoResetEvent2 по-прежнему занят. Поскольку потоки все еще ждут, ни какого побочного эффекта для объекта hAutoResetEvent1 не возникает.
Наконец освобождается и объект hAutoResetEvent2 В этот момент один из пото ков обнаруживает, что освободились оба объекта, которых он ждал. Его ожидание успешно завершается, оба объекта снова переводятся в занятое состояние, и выпол нение потока возобновляется. А что же происходит со вторым потоком? Он продол жает ждать и будет делать это, пока вновь не освободятся оба объекта-события.
Как я уже упоминал, WaitForMiltipleObjects работает на уровне атомарного досту па, и это очень важно. Когда она проверяет состояние объектов ядра, никто не может «у нее за спиной» изменить состояние одного из этих объектов. Благодаря этому ис ключаются ситуации со взаимной блокировкой. Только представьте, что получится, если один из потоков, обнаружив освобождение hAutoResetEventl, сбросит его в заня тое состояние, а другой поток, узнав об освобождении hAutoResetEvent2, тоже переве дет его в занятое состояние. Оба потока просто зависнут, первый будет ждать осво бождения объекта, захваченного вторым потоком, а второй — освобождения объек та, захваченного первым. WaitForMultipleObjects гарантирует, что такого не случится никогда.
Тут возникает интересный вопрос. Если несколько потоков ждет один объект ядра, какой из них пробудится при освобождении этого объекта? Официально Microsoft отвечает на этот вопрос так: «Алгоритм действует честно" Что это за алгоритм, Micro soft не говорит, потому что нс хочст связывать себя обязательствами всегда придер живаться именно этого алгоритма. Она утверждает лишь одно- если объект ожидает ся несколькими потоками, то всякий раз, когда этот объект переходит в свободное состояние, каждый из них получает шанс на пробуждение.
Таким образом, приоритет потока не имеет значения- поток с самым высоким приоритетом не обязательно первым захватит объект. Не получает преимущества и поток, который ждал дольше всех. Есть даже вероятность, что какой-то поток сумеет повторно захватить объект. Конечно, это было бы нечестно по отношению к другим потокам, и алгоритм пытается не допустить этого. Но никаких гарантий нет.
На самом деле этот алгоритм просто использует популярную схему "первым во шел — первым вышел" (FIFO). B принципе, объект захватывается потоком, ждавшим дольше всех. Но в системе могут произойти какие-то события, которые повлияют на окончательное решение, и ил-за этого алгоритм становится менее предсказуемым. Вот почему Microsoft и не хочет говорить, как именно он работает. Одно из таких собы тий — приостановка какого-либо потока. Если поток ждет объект и вдруг приоста навливается, система просто забывает, что он ждал этот объект. А причина в том, что нет смысла планировать приостановленный поток. Когда он в конце концов возоб новляется, система считает, что он только что начал ждать данный объект.
Учитывайте это при отладке, поскольку в точках прерывания (breakpoints) все потоки внутри отлаживаемого процесса приостанавливаются. Отладка делает алго ритм FIFO в высшей степени непредсказуемым из-за частых приостановки и возоб новления потоков процесса.