Работа с волокнами
Во-первых, потоки в Windows реализуются на уровне ядра операционной системы, которое отлично осведомлено об их существовании и "коммутирует" их в соответствии с созданным Microsoft алгоритмом В то же время волокня реализованы на уровне кода пользовательского режима, ядро ничего не знает о них, и процессорное время распределяется между волокнами по алгоритму, определяемому Вами. А раз так, то о вытеснении волокон говорить не приходится — по крайней мере, когда дело касается ядра
Второе, о чем следует помнить, — в потоке может быть одно или несколько воло кон. Для ядра поток — все то, что можно вытеснить и что выполняет код. Единовременно поток будет выполнять код лишь одного волокна — какого, решать Вам (соответствующие концепции я поясню позже).
Приступая к работе с волокнами, прежде всего преобразуйте существующий по ток в волокно. Это делает функция ConvertThreadToFiber
PVOID ConvertThreadToFiber(PVOID pvParam);
Она создает в памяти контекст волокна (размером около 200 байтов). В него входят следующие элементы:
В регистры процессора, включая указатели стека и команд.
Создав и инициализировав контекст волокна, Вы сопоставляете его адрес с потоком, преобразованным в волокно, и теперь оно выполняется в этом потоке 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. вместо вызовов этих функций он подставляет их код)