Common Intermediate Language и системное программирование в Microsoft.Net. Макаров А.В - 114 стр.

UptoLike

Есть частный случай применения функции Sleep – при задании ин-
тервала 0 вызов функции просто приводит к срабатыванию планировщи-
ка и, при наличии других готовых потоков, к их активации. Аналогичного
эффекта можно добиться, применяя функцию SwitchToThread, вызываю-
щую перепланирование потоков.
Поток может быть создан в приостановленном (suspended) состоянии с
помощью задания специального флага CREATE_SUSPENDED при вызове функ-
ций _beginthreadex или CreateThread, а также переведен в это состояние
(функция SuspendThread) или, наоборот, пробужден с помощью функции
ResumeThread.
6.2.4.3. Работа с волокнами
Работа с волокнами в приложении в чем-то сложнее, в чем-то проще.
Сложнее, потому что необходимо реализовать собственный планировщик
волокон. Сложность разработки планировщика резко возрастает при
необходимости синхронизации волокон – стандартные средства синхро-
низации Windows переводят в режим ожидания поток целиком, даже если
он должен планировать множество волокон. Проще, потому что все во-
локна могут разделять один поток – в этом случае легко избежать проблем
конкурирующего доступа к данным и можно применять любую библиоте-
ку времени исполнения, в том числе потоко-небезопасную.
При работе с волокнами используется функция ConvertThreadToFiber
для предварительного создания необходимых операционной системе струк-
тур данных. Функция ConvertFiberToThread выполняет обратную задачу и
уничтожает выделенные данные. После того как необходимые структуры со-
зданы (поток «превращен» в волокно), появляется возможность создавать но-
вые волокна (CreateFiber), удалять существующие (DeleteFiber) и планиро-
вать их исполнение (SwitchToFiber).
Приведем пример применения двух рабочих волокон, выполняющих
целевую функцию, и одного управляющего, удаляющего рабочие волокна
по их завершении.
Функция main превращает текущий поток в волокно (инициализация
внутренних структур данных для работы с волокнами), затем создает рабо-
чие волокна и организует цикл, в котором ожидает их завершения и удаля-
ет. Цикл завершается тогда, когда все рабочие волокна удалены, после че-
го функция main принимает меры к корректному завершению работы с во-
локнами.
Собственно целевая функция FiberProc эпизодически вызывает
функцию SwitchToFiber для переключения выполняемого волокна. В дан-
ном примере для определения нового волокна, подлежащего исполнению,
реализован простейший планировщик (функция schedule, инкапсулирую-
щая вызов функции SwitchToFiber).
Основы многозадачности
215
В данном примере можно было бы создавать поток не вызовом функ-
ции _beginthreadex (или _beginthread), а вызовом функции API
CreateThread. Но при незначительном усложнении примера, скажем, соз-
дании не одного, а двух потоков, уже было бы возможно возникновение
ошибки при одновременном обращении к операторам new или delete в
разных потоках (причем именно «возможно», так как ничтожные времен-
ные задержки могут изменить поведение потоков – это крайне осложняет
выявление таких ошибок). Применение функций библиотеки времени ис-
полнения для создания потоков решает эту проблему.
Windows содержит достаточно богатый набор функций для управле-
ния потоками, включающий функции создания и завершения потоков
(функции API CreateThread, ExitThread, TerminateThread и их «обертки» в
библиотеке времени исполнения _beginthread, _endthread, _beginthreadex
и _endthreadex).
Функция Sleep(DWORD dwMilliseconds) может переводить поток в
«спячку» на заданное время. Продолжительность задается с точностью до
кванта работы планировщика, то есть не лучше, чем 10-15 мс, несмотря на
то, что при вызове функции задать можно до 1 мс. Измерение времени ре-
альной паузы, заданной, например, вызовом Sleep(1), позволяет получить
косвенную информацию о работе планировщика.
В Windows существует интересная особенность, связанная с работой
планировщика и измерением интервалов времени. Система предоставля-
ет три способа измерения интервалов:
таймер низкого разрешения, основанный на квантах планиров-
щика (GetTickCount);
«мультимедийный», с разрешением до 1 мс (timeGetTime,
timeBeginPeriod и пр.);
высокоточный, использующий счетчик тактов процессора и с
разрешением ощутимо лучше микросекунды на современных
процессорах (QueryPerformanceCounter,
QueryPerformanceFrequency).
Обычно мультимедийный таймер работает с разрешением от 1-5 мс и ху-
же (зависит от аппаратуры), однако функция timeBeginPeriod позволяет изме-
нить разрешение вплоть до 1 мс. Если стандартное разрешение мультимедий-
ного таймера на данном компьютере хуже 5-10 мс, то у функции
timeBeginPeriod есть побочный эффект – улучшение разрешения повлияет на
работу планировщика во всей системе, а не только в процессе, вызвавшем эту
функцию. В результате, если один процесс повысит разрешение мультимедий-
ного таймера, то функция Sleep также получит возможность задавать интерва-
лы вплоть до 1 мс и эффект наблюдается даже в других процессах. Если муль-
тимедийный таймер на данной аппаратуре стандартно работает с разрешением
порядка 1 мс, то такого влияния на планировщик не наблюдается.
214
CIL и системное программирование в Microsoft .NET
214                          CIL и системное программирование в Microsoft .NET   Основы многозадачности                                               215


      В данном примере можно было бы создавать поток не вызовом функ-                 Есть частный случай применения функции Sleep – при задании ин-
ции _beginthreadex (или _beginthread), а вызовом функции API                     тервала 0 вызов функции просто приводит к срабатыванию планировщи-
CreateThread. Но при незначительном усложнении примера, скажем, соз-             ка и, при наличии других готовых потоков, к их активации. Аналогичного
дании не одного, а двух потоков, уже было бы возможно возникновение              эффекта можно добиться, применяя функцию SwitchToThread, вызываю-
ошибки при одновременном обращении к операторам new или delete в                 щую перепланирование потоков.
разных потоках (причем именно «возможно», так как ничтожные времен-                   Поток может быть создан в приостановленном (suspended) состоянии с
ные задержки могут изменить поведение потоков – это крайне осложняет             помощью задания специального флага CREATE_SUSPENDED при вызове функ-
выявление таких ошибок). Применение функций библиотеки времени ис-               ций _beginthreadex или CreateThread, а также переведен в это состояние
полнения для создания потоков решает эту проблему.                               (функция SuspendThread) или, наоборот, пробужден с помощью функции
      Windows содержит достаточно богатый набор функций для управле-             ResumeThread.
ния потоками, включающий функции создания и завершения потоков
(функции API CreateThread, ExitThread, TerminateThread и их «обертки» в          6.2.4.3. Работа с волокнами
библиотеке времени исполнения _beginthread, _endthread, _beginthreadex                 Работа с волокнами в приложении в чем-то сложнее, в чем-то проще.
и _endthreadex).                                                                 Сложнее, потому что необходимо реализовать собственный планировщик
      Функция Sleep(DWORD dwMilliseconds) может переводить поток в               волокон. Сложность разработки планировщика резко возрастает при
«спячку» на заданное время. Продолжительность задается с точностью до            необходимости синхронизации волокон – стандартные средства синхро-
кванта работы планировщика, то есть не лучше, чем 10-15 мс, несмотря на          низации Windows переводят в режим ожидания поток целиком, даже если
то, что при вызове функции задать можно до 1 мс. Измерение времени ре-           он должен планировать множество волокон. Проще, потому что все во-
альной паузы, заданной, например, вызовом Sleep(1), позволяет получить           локна могут разделять один поток – в этом случае легко избежать проблем
косвенную информацию о работе планировщика.                                      конкурирующего доступа к данным и можно применять любую библиоте-
      В Windows существует интересная особенность, связанная с работой           ку времени исполнения, в том числе потоко-небезопасную.
планировщика и измерением интервалов времени. Система предоставля-                     При работе с волокнами используется функция ConvertThreadToFiber
ет три способа измерения интервалов:                                             для предварительного создания необходимых операционной системе струк-
        • таймер низкого разрешения, основанный на квантах планиров-             тур данных. Функция ConvertFiberToThread выполняет обратную задачу и
           щика (GetTickCount);                                                  уничтожает выделенные данные. После того как необходимые структуры со-
        • «мультимедийный», с разрешением до 1 мс (timeGetTime,                  зданы (поток «превращен» в волокно), появляется возможность создавать но-
           timeBeginPeriod и пр.);                                               вые волокна (CreateFiber), удалять существующие (DeleteFiber) и планиро-
        • высокоточный, использующий счетчик тактов процессора и с               вать их исполнение (SwitchToFiber).
           разрешением ощутимо лучше микросекунды на современных                       Приведем пример применения двух рабочих волокон, выполняющих
           процессорах (QueryPerformanceCounter,                                 целевую функцию, и одного управляющего, удаляющего рабочие волокна
           QueryPerformanceFrequency).                                           по их завершении.
      Обычно мультимедийный таймер работает с разрешением от 1-5 мс и ху-              Функция main превращает текущий поток в волокно (инициализация
же (зависит от аппаратуры), однако функция timeBeginPeriod позволяет изме-       внутренних структур данных для работы с волокнами), затем создает рабо-
нить разрешение вплоть до 1 мс. Если стандартное разрешение мультимедий-         чие волокна и организует цикл, в котором ожидает их завершения и удаля-
ного таймера на данном компьютере хуже 5-10 мс, то у функции                     ет. Цикл завершается тогда, когда все рабочие волокна удалены, после че-
timeBeginPeriod есть побочный эффект – улучшение разрешения повлияет на          го функция main принимает меры к корректному завершению работы с во-
работу планировщика во всей системе, а не только в процессе, вызвавшем эту       локнами.
функцию. В результате, если один процесс повысит разрешение мультимедий-               Собственно целевая функция FiberProc эпизодически вызывает
ного таймера, то функция Sleep также получит возможность задавать интерва-       функцию SwitchToFiber для переключения выполняемого волокна. В дан-
лы вплоть до 1 мс и эффект наблюдается даже в других процессах. Если муль-       ном примере для определения нового волокна, подлежащего исполнению,
тимедийный таймер на данной аппаратуре стандартно работает с разрешением         реализован простейший планировщик (функция schedule, инкапсулирую-
порядка 1 мс, то такого влияния на планировщик не наблюдается.                   щая вызов функции SwitchToFiber).