Операционные системы. Многозадачность и многопотоковость, страница 3

Чем вытесняющая многопоточность выгоднее механизма диспетчеризации, используемого в 16-ти разрядном Windows 3.x?

Основная экономия заключается в том, что переключение контекста происходит реже, не каждый раз, как истечет квант времени. Если по истечении кванта не найдется потока с большим приоритетом, или больший приоритет будет у потока, работающего в том же самом процессе, то контекст переключать не надо. А в Windows 3.x переключение происходи по циклу при каждом кванте.

Синхронизация потоков (продолжение)

Давайте рассмотрим оставшиеся механизмы синхронизации потоков. Итак, мы уже знакомы со взаимоисключением и  семафорами. Помимо этого в ОС используются мониторы.

Мониторы

Это механизм синхронизации процессов (потоков), который содержит как данные, так и процедуры, необходимые для реализации динамического распределения конкретного общего ресурса. Чтобы обеспечит выделение нужного ему ресурса, процесс должен обратиться к конкретной процедуре монитора. Естественно, что необходимость получить доступ к ресурсу может возникнуть одновременно у нескольких процессов. Но вход в монитор находится под контролем - здесь осуществляется взаимоисключение процессов. В каждый момент времени только одному процессу разрешается войти в монитор. Процессам, которые хотят войти в монитор, придется ждать, если он уже занят. Причем, в отличии от семафоров, процессом ожидания управляет сам монитор.

Внутренние данные монитора доступны только изнутри его. Подобный пример инкапсуляции данных и обрабатывающих эти данные процедур мы с Вами еще рассмотрим при изучении ООП.

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

Например,

Lock::Acquire - ожидать, пока критический участок не освободится, а затем захватить его.

Lock::Release - освободить, разбудить процесс, который ожидает в функции Acquire.

Вспомним правила взаимоисключения:

n прежде чем получить доступ к разделяемым данным, необходимо осуществить исключение доступа к ним других

n после завершения обработки разделяемых данных необходимо освободить их и тем самым разрешить их захват другим процессам для обработки.

Простой пример синхронизации при обработке списка:

AddToQueue ()

{

lock.Acquire(); // захватить перед обработкой

PutItemToQueue(); // Обработать разделяемые данные

lock.Realise(); // Освободить после обработки

}

RemoveFromQueue()

{

lock.Accuire();

if(AnythingIsInQieue()) RemoveItemFromQueue();

lock.Realise();

}

Все хорошо? А если в списке нет данных? Как надо изменить RemoveFromQueue() чтобы эта функция могла подождать, чтобы данные появились в списке.

Вообще то, можно было бы подождать (уснуть) внутри критической секции, но тогда, если запереть секцию, пока ждем, то как же кто-либо в нее войдет, чтобы добавить элемент в список?

Новая идея: пускай спит внутри критической секции (раз уж вошла в нее, не выгонять же теперь), но пусть “отопрет “ вход в нее на то время, пока спит. Как это сделать? Для этого придумали условные переменные (переменные-условия). Это очередь потоков, которые ожидают входа в критическую секцию. Условные переменные поддерживают три операции:

Wait() - освободить исключение, “уснуть”, перехватить исключение. Освобождение исключения и засыпание являются непрерывными (atomic).

Signal() - Разбудить спящего, если что-то произошло

Broadcast() - разбудить всех спящих

При выполнении условных операций критическая секция должна быть захвачена.

Модифицируем пример синхронизации с использованием условных переменных.

AddToQueue ()

{

lock.Acquire(); // захватить перед обработкой

PutItemToQueue(); // Обработать разделяемые данные

condition.signal(lock); // оповестить ожидающего об освобождении данных

lock.Realise(); // Освободить после обработки

}

RemoveFromQueue()

{

lock.Accuire();

//        if(AnythingIsInQieue())

while (!(AnythingIsInQieue()) condition.wait (&lock); // освободить, уснуть, проснуться после сигнала

RemoveItemFromQueue();

lock.Realise();

}

Поскольку у процесса могут быть различные причины для ожидания в мониторе, то для каждой отдельной причины назначается собственная условная переменная (в рассмотренном случае - lock).