Windows

       

Работа с волокнами


Во-первых, потоки в Windows реализуются на уровне ядра операционной системы, которое отлично осведомлено об их существовании и "коммутирует» их в соответ ствии с созданным Microsoft алгоритмом В то жс время волокня реализованы на уров не кода пользовательского режима, ядро ничего не знает о них, и процессорное вре мя распределяется между волокнами по алгоритму, определяемому Вами. А раз так, то о вытеснении волокон говорить не приходится — по крайней мере, когда дело каса ется ядра

Второе, о чем следует помнить, — в потоке может быть одно или несколько воло кон. Для ядра поток — все то, что можно вытеснить и что выполняет код. Единовре менно поток будет выполнять код лишь одного волокна — какого, решать Вам (соот ветствующие концепции я поясню позже).

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

PVOID ConvertThreadToFiber(PVOID pvParam);

Она создает в памяти контекст волокна (размером около 200 байтов). С него вхо дят следующие элементы:

  • определенное программистом значение; оно получает значение параметра pvParam, передаваемого в ConvertThreadToFiber,

  • заголовок цепочки структурной обработки исключений,

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

    Создав и инициализировав контекст волокна, Вы сопоставляете его адрес с пото ком, преобразованным в волокно, и теперь оно выполняется в этом потоке Convert ThreadToFiber возвращает адрес, по которому расположен контекст волокна. Этот ад pcc сщe понадобится Вам, но ни считывать, ни записывать по нему напрямую ни в коем случае нельзя — с содержимым этой структуры работают только функции, уп равляющие волокнами При вызове ExitThread завершаются и волокно, и поток

    Нет смысла преобразовывать поток в волокно, если Вы не собираетесь создавать дополнительные волокна в том же потоке Чтобы создать другое волокно, поток (вы полняющий в данный момент волокно), вызывает функцию CreateFiber:






    PVOID CreateFiber( DWORD dwStackSize, PFIBER_START_ROUTINE pfnStartArtrtress, PVOID pvParam);

    Сначала она пытается создать новый стек, размер которого задан в параметре dwStackSize. Обычно передают 0, и тогда максимальный размер стека ограничивается 1 Мб, а изначально ему передается две страницы памяти. Если Вы укажече ненулевое значение, то для стека будет зарезервирован и передан именно такой объем памяти.

    Создав стек, CrealeFiber формирует новую структуру контекста волокна и иници ализирует ее. При этом первый ее элемент получает значение, переданное функции как параметр pvParam, сохраняются начальный и конечный адреса стека, а также адрес функции волокна (переданный как аргумет pfnStartAddress)

    У функции волокна, реализуемой Вами, должен бьпь такой прототип;

    VOID WINAPI FiberFunc(PVOID pvParam);

    Она выполняется, когда волокно впервые получает управление В качестве пара метра ей передается значение, изначально переданное как аргумент pvParam функ ции CreateFtber Внутри функции волокна можно делать что угодно. Обратите внима ние на тип возвращаемого значения — VOID Причина не в том, что это значение несущественно, а в том, чго функция никогда не возвращает управление! А иначе поток и все созданные внутри него волокна были бы тут же уничтожены.

    Как и ConvertThreadToFiber, CreateFiber возвращает адрес контекста волокна, но с тем отличием, что новое волокно начинает работать не сразу, поскольку продолжа ется выполнение текущего. Единовременно поток может выполнять лишь одно волок но. И, чтобы новое волокно стало работать, надо вызвать SwitchToFiber

    VOID SwitchToFiber(PVOID pvFiberExeculionContext);

    Эта функция принимает единственный параметр (pvFiberExecutionContext) — ад рес контекста волокна, полученный в предшествующем вызове ConvertThreadToFiber или CreateFiber По этому адресу она определяет, какому волокну предоставить про цессорное время SwitchToFiber осуществляет такие операции

  • Сохраняет в контексте выполняемого в данный момент волокна ряд текущих регистров процессора, включая указатели команд и стека



  • Загружает в регистры процессора значения, ранее сохраненные в контексте волокна, подлежащею выполнению В их число входит указатель стска, и по этому при переключении на другое волокно используется именно его стек

  • Связывает контекст волокна с потоком, и тот выполняет указанное волокно

  • Восстанавливает указатель команд Поток (волокно) продолжает выполнение с того места, на каком волокно было прервано в последний раз


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

    Для уничтожения волокна предназначена функция DeleteFiber

    VOID DeleteFiber(PVOID pvFiberExecutionContext);

    Она удаляет волокно, чей адрес контекста определяется параметром pvFtberExecu tionContext, освобождает память, занятую стеком волокна, и уничтожает его контекст Но, если Вы передаете адрес волокна, связанного в данный момент с потоком, Delete Fiber сама вызывает ExitThread — в результате поток и все созданные в нем волокна «погибают»

    DeleteFiber обычно вызывается волокном, чтобы удалить другое волокно Стек уда ляемого волокна уничтожается, а его контекст освобождается И здесь обратите вни мание на разницу между волокнами и потоками потоки, как правило, уничтожают себя сами, обращаясь к ExitThread Использование с этой целью TerminateThread счи тается плохим тоном — ведь тогда система не уничтожает стек потока Так вот, спо собность волокна корректно уничтожать другие волокна нам еще пригодится — как именно, я расскажу, когда мы дойдем до программы-примера

    Для удобства предусмотрено еще две функции, управляющие волокнами В каж дый момент потоком выполняется лишь одно волокно, и операционная система все гда знает, какое волокно связано сейчас с потоком Чтобы получить адрес контекста текущего волокна, вызовите GetCurrentFiber

    PVOID GetCurrentFiber();

    Другая полезная функция — GetFiberData

    PVOID GetFiberData();

    Как я уже говорил, контекст каждого волокна содержит определяемое програм мистом значение Оно инициализируется знячением параметра pvParam, передавае мого функции ConvertThreadToFiber или CreateFiber, и служит аргументом функции во локна GetFtberData просто «заглядывает» в контекст текущего волокна и возвращает хранящееся там значение

    Обе функции — GetCurrentFiber и GetFiberData — работают очень быстро и обыч но реализуются компилятором как встраиваемые (т e. вместо вызовов этих функций он подставляет их код)


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