- •Введение.
- •Вопрос№1 Определение операционной системы
- •Вопрос№2 Структура операционной системы
- •Вопрос№3 Типы операционных систем
- •Вопрос№4 Объекты и их дескрипторы в ос Windows
- •Потоки и процессы
- •Вопрос№5 Поток управления
- •Вопрос№6 Состояния потока
- •Планирование процессов
- •Вопрос№11 Управление исполнением процессов
- •3.8. Задачи на обслуживание непрерываемых процессов
- •3.9. Задачи на обслуживание прерываемых процессов
- •4. Синхронизация потоков
- •5. Примитивы синхронизации
- •6. Классические задачи синхронизации
- •6.5. Задача Дейкстры об обедающих философах
- •Вопрос 1. Требуется определить, сколько ресурсов дополнительно можно выделить процессу j, где
- •Вопрос 2. Процесс 3 запросил у системы 3 единицы ресурса r. Удовлетворит ли система этот запрос?
- •8. Передача данных между процессами.
- •9. Управление устройствами компьютера
- •10. Виртуальная память
- •11. Управление файлами
5. Примитивы синхронизации
Вопрос№26 5.1. Блокировка потоков
Примитив синхронизации – это программное или аппаратное средство (механизм, инструмент) высокого уровня для решения задач синхронизации потоков. Примитивы синхронизации обычно реализованы как объекты ядра ОС.
Рассмотрим реализацию примитивов синхронизации только для однопроцессорных компьютеров. В этом случае атомарность действий можно обеспечить посредством запрещения прерываний. Чтобы избежать активного ожидания, будем блокировать исполнение потоков в том случае, если примитив синхронизации уже используется (занят) другим потоком. Для этого с каждым примитивом синхронизации свяжем очередь заблокированных потоков, ждущих освобождения этого примитива синхронизации.
Спецификация очереди блокированных потков
class ThreadQueue
{
Thread* tp; //список потоков
public:
ThreadQueue():tp(NULL){}
~ThreadQueue(){...}//здесь нетривиально
void unqueueThread(Thread&t);
bool dequeueThread();
};
void ThreadQueue::enqueueThread(Thread&t)
{ includeThreadToList(t);
t.suspendThread();
}
bool ThreadQueue::dequeueThread()
{
Thread t;
if (!tp)
return false;
else
{
t = excludeThreadFromQueue();
t.resumeThread();
return true;
}
}
Вопрос№27 5.2. Примитив синхронизации Lock (замок)
Схема реализации примитива синхронизации Lock
class Lock
{
bool free;
ThreadQueue tq; //очередь потоков
public:
Lock():free(true){}
~Lock(){…}
void acquire(); //закрываем замок
void release(); //открываем замок
};
void Lock::acquire() //закрываем замок
{
disableInterrupt();
if (!free)
tq.enqueueThread(currentThread());
else
free = false;
enableInterrupt();
}
void Lock::release() //открываем замок
{
disableInterrupt();
if (!tq.dequeueThread())
free = true;
enableInterrupt();
}
Покажем, как с помощью примитива синхронизации Lock можно решить проблему взаимного исключения. Для этого рассмотрим следующие потоки:
Lock lock;
void thread_1()
{
…
lock.acquire():
if (n%2==0)
n=a;
else
n=b;
lock.release();
…
}
void thread_2()
{
…
lock.acquire();
++n;
lock.release();
…
}
В ОС Windows аналогами примитива синхронизации Lock являются примитивы синхронизации CRITICAL_SECTION и Mutex.
Вопрос№28 5.3. Примитив синхронизации Condition (условие)
Схема реализации примитива синхронизации Condition
class Condition
{
bool event;
ThreadQueue tq; //очередь потоков
public:
Condition():event(false){}
~Condition(){…}
void wait(); //ждем выполнения условия
void signal(); //сигнализируем о выполнении условия
};
//ждем выполнения условия
void Condition::wait()
{
disableInterrupt();
if (event)
event = false;
else
tq.enqueueThread(currentThread());
enableInterrupt();
}
//сигнализируем о выполнении условия
void signal()
{
disableInterrupt();
if (!tq.dequeueThread())
event = true;
enableInterrupt();
}
Покажем, как с помощью примитива синхронизации Condition можно решить проблему условной синхронизации. Для этого рассмотрим следующие потоки.
Condition c; //начальное состояние несигнальное
void thread_1()
{
…
c.wait();
if (n%2==0)
n=a;
else
n=b;
…
}
void thread_2()
{
…
n++;
c.signal();
…
}
Покажем, как с помощью примитива синхронизации Condition можно решить проблему взаимного исключения. Для этого рассмотрим следующие потоки.
Condition c; //начальное состояние несигнальное
с.signal(); //устанавливаем в сигнальное состояние
void thread_1()
{
…
c.wait();
if (n%2==0)
n=a;
else
n=b;
…
}
void thread_2()
{
…
с.wait();
n++;
c.signal();
…
}
В OС Windows аналогом примитива синхронизации Сondition является примитив синхронизации Event.
Вопрос№29 5.4. Семафоры Дейкстры
Cемафор – это неотрицательная целочисленная переменная, значение которой может изменяться только при помощи атомарных операций.
Семафор считается свободным, если его значение больше нуля, в противном случае семафор считаетсязанятым.
Пусть s – семафор, тогда над ним можно определить следующие атомарные операции:
P(s) //захватить семафор
{
если s>0
то s = s-1; //поток продолжает работу
иначе
ждать освобождения s; //поток переходит в состояние ожидания
}
V(s) //освободить семафор
{
если потоки ждут освобождения s
то освободить один поток
иначе s = s+1;
}
Cемафор с операциями P и V называется семафором Дейкстры, голландского математика, который первым использовал семафоры для решения задач синхронизации.
Из определения операций над семафором видно, что если поток выдает операцию P и значение семафора больше нуля, то значение семафора уменьшается на 1, и этот поток продолжает свою работу, в противном случае поток переходит в состояние ожидания до освобождения семафора другим потоком.
Выввести из состояния ожидания поток, который ждет освобождения семафора, может только другой поток, который выдает операцию V над этим же семафором.
Потоки, ждущие освобождения семафора, выстраиваются в очередь к этому семафору. Дисциплина обслуживания очереди зависит от конкретной реализации. Очередь может обслуживаться как по правилу FIFO, так и при помощи более сложных алгоритмов, учитывая приоритеты потоков. Если очередь семафора обслуживается по алгоритму FIFO, то семафор называется сильным, иначе – слабым.
Семафор, который может принимать только значения 0 или 1, называется двоичным или бинарным семафором.
Семафор, значение которого может быть больше 1, обычно называют считающим семофором.
Решение проблемы взаимного исключения с помощью семафора
Semaphor s=1; //семафор свободен
void thread_1()
{
…
P(s);
if (n%2==0)
n=a;
else
n=b;
V(s);
…
}
void thread_2()
{
…
P(s);
n++;
V(s);
…
}
Решение проблемы условной синхронизации с помощью семафора
Semaphor s=0; //семафор свободен
void thread_1()
{
…
P(s);
if (n%2==0)
n=a;
else
n=b;
…
}
void thread_2()
{
...
n++;
V(s);
…
}
Вопрос№30 5.5. Примитив синхронизации Semaphore (семафор)
Схема реализации примитива синхронизации семафор
class Semaphore
{
int count; //счетчик
ThreadQueue tq; // очередь потоков
public:
Semaphore(int&n):count(n){}
~Semaphore(){…}
void wait(); //закрыть семафор
void signal(); //открыть семафор
}
void Semaphore::wait() //закрыть семафор
{
disableInterrupt();
if (count>0)
--count;
else
tq.enqueueThread(currentThread());
enableInterrupt();
}
void Semaphore::signal() //открыть семафор
{
disableInterrupt();
if (!tq.dequeueThread())
++count;
enableInterrupt();
}
Вопрос№31 5.6. Объекты синхронизации и функции ожидания в Windows
Объекты синхронизации
В ОС Windows объектами синхронизации называются объекты ядра, которые могут находиться в одном из двух состояний:
- сигнальном (signaled);
- несигнальном (nonsignaled).
Объекты синхронизации могут быть разбиты на 3 класса:
- собственно объекты синхронизации, которые служат только для решения проблемы синхронизации параллельных потоков. К таким объектам синхронизации в Windows относятся:
- мьютекс (mutex);
- событие (event);
- семафор (semaphore);
- ожидающий таймер (waitable timer);
- объекты, которые переходят в сигнальное состояние по завершении своей работы или при получении некоторого сообщения, например, потоки и процессы. Пока эти объекты выполняются, они находятся в несигнальном состоянии. Если выполнение этих объектов заканчивается, то они переходят в сигнальное состояние.
Функции ожидания
Функции ожидания в Windows это такие функции, которые используются для блокировки исполнения потоков в зависимости от состояния объекта синхронизации, который является параметром функции ожидания. При этом блокировка потоков выполняется следующим образом:
- если объект синхронизации находится в несигнальном состоянии, то поток, вызвавший функцию ожидания, блокируется до перехода этого объкта синхронизации в сигнальное состояние;
- если объект синхронизации находится в сигнальном состоянии, то поток, вызвавший функцию ожидания, продолжает свое исполнение, а объект синхронизации, как правило, переходит в несигнальное состояние.
Функции ожидания в Windows:
- WaitForSingleObject- ждет перехода в сигнальное состояние одного объекта синхронизации;
- WaitForMultipleObjects – ждет перехода в сигнальное состояние одного или нескольких объектов из массива объектов синхронизации
Вопрос№32 5.7. Критические секции в Windows
Для решения проблемы взаимного исключения для параллельных потоков, выполняемых в контексте одного процесса, в ОС Windows предназначены объекты типа CRITICAL_SECTION. Объект типа CRITICAL_SECTION не является объектом ядра ОС, так как предполагается его использование только в контексте одного процесса. Это повышает скорость работы этого примитива синронизации, так как не требуется обращаться к ядру ОС для доступа к памяти другого процесса.
Для работы с объектами типа CRITICAL_SECTION используются следующие функции:
- InitializeCriticalSection – инициализация объекта;
- EnterCriticalSection – вход в критическую секцию;
- TryEnterCriticalSection – попытка входа в критическую секцию;
- LeaveCriticalSection – выход из критической секции;
- DeleteCriticalSection – завершение работы с объектом
Вопрос№33 5.8. Мьютексы в Windows
Для решения проблемы взаимного исключения для параллельных потоков, выполняющихся в контексте разных процессов, в ОС Windows предназначен объект ядра мьютекс (mutex).
Мьютекс находится в сигнальном состоянии, если он не принадлежит ни одному потоку. В противном случае мьютекс находится в несигнальном состоянии. Одновременно мьютекс может принадлежать только одному потоку.
Для работы с мьютеками используются следующие функции:
- CreateMutex – создание мьютекса;
- OpenMutex – получение доступа к существующему мьютексу;
- ReleaseMutex – освобождение мьютекса (переход мьютекса в сигнальное состояние);
- WaitForSingleObject или
- WaitForMultipleObjects – захват мьютекса (ожидание сигнального состояния мьютекса).
Вопрос№34 5.9. События в Windows
В ОС Windows события описываются объектами ядра Event. При этом различают два типа событий:
- события с ручным сбросом;
- события с автоматическим сбросом.
Что лучше: +2 или -2?
Событие с ручным сбросом можно перевести в несигнальное состояние только посредством вызова функции ResetEvent. Событие с автоматическим сбросом переходит в несигнальное состояние как при помощи функции ResetEvent, так и при помощи функций ожидания WaitForSingleObject и WaitForMultipleObjects. Если события с автоматическим сбросом ждут несколько потоков, используя функцию WaitForSingleObject, то из состояния ожидания освобождается только один из этих потоков.
Для работы с событиями используются следующие функции:
- CreateEvent – создания события;
- OpenEvent – получение доступа к существующему событию;
- SetEvent – перевод события в сигнальное состояние;
- ResetEvent – перевод события в несигнальное состояние;
- PulseEvent – освобождение нескольких потоков, ждущих сигнального состояния события с ручным сбросом;
- WaitForSingleObject или WaitForMultipleObjects – ожидание наступления события (перехода события в сигнальное состояние)
Вопрос№35 5.10. Семафоры в Windows
Семафоры в ОС Windows описываются объектами ядра Semaphore. Семафор находится в сигнальном состоянии, если его значение больше нуля. В противном случае семафор находится в несигнальном состоянии.
Для работы с семафорами используются следующие функции:
- CreateSemaphore – создание семафора;
- OpenSemaphore – получение доступа к существующему семафору;
- ReleaseSemaphore – увеличение значения семафора на положительное число;
- WaitForSingleObject или WaitForMultipleObjects – ожидание перехода семафора в сигнальное состояние.