|
Проблемы с терминологией
Запуск нити
Работа с нитью
В русской литературе встречаются различные названия одного
и того же явления. Иногда совпадающие названия используются для определения разных
явлений. Поэтому имеет смысл договорится о терминологии сразу.
Начиная с 286 процессора Intel ввела возможность создавать параллельно работающие
задачи. Это достигается аппаратной поддержкой процессора при переводе оного
в так называемый защищенный режим. Переключение между задачами возложено на программную
часть, которую должна содержать операционная система. Этот модуль имеет название
менеджера многозадачности.
В 386 процессоре и более новых возможности защищенного режима расширены. Современные
операционные системы используют полностью 32-битный код. Каждая задача запускается
в собственном адресном пространстве с моделью памятью flat. Это означает, что все
указатели имеют ближний тип (near). Входы в функции операционной системы отображаются
на достаточно далекие адреса. В полуоси (до версии 4.0) они начинаются с 512 Мб.
Можно наблюдатся такую многозадачность при запуске различных программ одновременно,
например запустив две копии cmd.exe в разных окнах.
В некоторых операционных системах этим ограничиваются (Linux). В других введена
возможность запуска так называемых нитей. Нить на самом деле не является
отдельной задачей и представляет собой просто реентерабельную процедуру в текущей
задаче. Вызвав соответствующую функцию API можно сказать менеджеру мультизадачности,
что необходимо нить переключать так же, как и другие задачи системы. Ранее было
употребительно название для нитей поток. Однако этим же словом называется
интерфейс работы с потоковыми устройствами (файлами). Поэтому лучше употреблять
слово нить. Сама же возможность работы с многими нитями имеет устойчивое название
многопоточность.
В NT введено еще более мелкое деление: можно создавать так называемые волокнавнутри
нити. Эти волокна подобны нитям, только менеджер многозадачности не будет переключаться
между ними. Реализация многозадачности волокон возлагается полностью на программиста.
Как видно из вышесказанного, многопоточность имеет отношение к многозадачности весьма
опосредованное. Например возможна реализация многопоточности в DOS. Все, что требуется
- реентерабельность функций (которые могут становится нитями) и реализация менеджера
многозадачности (в DOS можно использовать прерывание таймера для переключения между
нитями).
В отличие от задач нити не имеют собственного пространства для данных. Единственное,
что имеется собственного у нити - это собственный стек.
С одной стороны, нити менее устойчивы к ошибкам программирования. Наиболее часто
встречающиеся проблемы - отсутствие реентрерабельности из-за изменения глобальных
переменных и переполнение стека нити. С другой стороны, запускаемая нить может использовать
все ресурсы основного процесса, от которого нить порождается. Запуск нити очень
быстр (быстрее запуска отдельной задачи).
Количество нитей, которые могут одновременно работать в OS/2 определяется, по-видимому,
размером соответствующей таблицы в менеджере многозадачности и задается строкой
в config.sys: threads=<число тредов>. Это число в настоящее время не может
превышать значения 4096. Не надо забывать, что каждая задача имеет по крайней мере
одну нить - основную нить процесса. Если будет запущено большее количество нитей,
то одновременно будет работать только часть из них, остальные будут блокированы
и работать в режиме невытесняющей многозадачности. Число 4096 является, по-видимому,
аппаратным ограничением процессоров Intel.
Основой для реализации многопоточности является функция API DosCreateThread.
Однако для использования в программе категорически рекомендуется использовать функцию,
предоставляемую стандартными библиотеками языка программирования BeginThread. Эта
функция кроме вызова API делает еще кое-какие шаги для подготовки многозадачности.
function BeginThread(
SecurityAttibutes: Pointer; // используется только в WinNT
StackSize: LongInt; // размер стека для создаваемой нити
ThreadFunc: TThreadFunc; // реентерабельная функция, которая станет нитью
Parameter: Pointer; // здесь мы можем передать нити наши параметры
CreationFlags: LongInt;; // здесь описывается, как именно инициализировать нить
var ThreadID: LongInt; // в переменной ThreadID мы получим номер запущенной нити
): LongInt;
TThreadFunc = function(Parameter: Pointer): LongInt;
В случае удовлетворительной работы функция BeginThread возвращает значение 0, иначе
мы получаем код ошибки.
StackSize рекомендуется делать достаточно большим, например 16000 (байт).
CreationFlags позволяет задать способы инициализации нити:
CREATE_READY = 00000000; // при создании нити запустить ее сразу
CREATE_SUSPEND = 00000001; // при создании нити блокировать
STACK_SPARCE = 00000000; // стэк выделять по умолчанию (динамическое выделение)
STACK_COMMITED = 00000002; // стэк выделится (с выравниванием на 4096 байт) и будет
зарезервирован.
Реально используют только первые два флага.
В принципе, это всё, чтобы начать работу с нитями. Все остальное - дополнительные
сервисы для управления.
Если нить запущена, то она работает до тех пор, пока не завершится функция
ThreadFunc. Поэтом в теле этой функции должно быть что-то вроде
while not FTerminated do Work;
FTerminated должна быть статической переменной, которой мы извне нити
можем присвоить значение true для завершения нити изнутри.
Если нить подвисла, ее можно убить со стороны, вызвав
function KillThread(ThreadID: LongInt): LongInt;
Заморозить нить можно, вызвав
function SuspendThread(ThreadID: LongInt): LongInt;
Оживление нити:
function ResumeThread(ThreadID: LongInt): LongInt;
Важный вопрос об установке приоритета нити решается вызовом DosSetPriority:
function DosSetPriority(
Scope: Ulong;
PrtyClass: ULong;
Delta: Long;
PorTID: ULong;
): APIRET;
Т.к. данная функция позволяет изменять приоритеты не только нитям,
но и задачам, то ее аргументы достаточно общи. Нам же понадобятся лишь некоторые
значения:
Scope := prtys_Thread; // = 2; Т.е. изменить значение только нити
PrtyClass может принимать значения prtyc_NoChange (не менять класс), prtyc_IdleTime,
prtyc_Regular, prtyc_TimeCritical, prtyc_ForegroudServer (не меняющийся динамически
TimeCritical).
Delta может иметь значения от -31 до 31.
PorTID := ThreadID; // этой переменой даем наш номер нити.
В Virtual Pascal отсутствует класс TThread. Причины этого непонятны, очевидно автор
хотел, чтобы все писали код сами. В качестве примера, где реализуется запуск нити
и работы с нитью я написал собственный класс TThread
Обсудить материал (число отзывов:0) предыдущий материал | следующий материал |