- •Multithreading - The Delphi Way. Многопоточность - как это делается в Дельфи.
- •Глава 8. Потокобезопасные классы в Дельфи и приоритеты 62
- •Глава 9. Семафоры. Управление потоками данных Взаимосвязь источник-приемник 70
- •Глава 10. Ввод/вывод и потоки данных: от блокировки к асинхронности и обратно 96
- •Глава 11. Синхронизаторы и события (Events). 121
- •Глава 12. Еще о возможностях синхронизации в Win32. 139
- •Глава 13. Использование потоков совместно с bde, Исключениями и dll 145
- •Глава 14. Проблемы, встретившаяся на практике, и их решение 163
- •Посвящения.
- •Введение.
- •Определения
- •Разделение времени
- •Для чего используют потоки?
- •Глава 2. Создание потока в Delphi. Предисловие с диаграммой.
- •Наш первый не vcl поток
- •Что именно делает эта программа?
- •Проблемы и сюрпризы.
- •Проблемы запуска.
- •Проблемы взаимодействия
- •Проблемы завершения
- •Глава 3. Основы синхронизации. Как разделять данные между потоками?
- •Атомарность при доступе к общим данным
- •Дополнительные проблемы с vcl
- •Многопроцессорные машины.
- •Решение для Delphi: tThread.Synchronize.
- •Как это работает? Что делает Synchronize?
- •Синхронизация для не-vcl потоков.
- •Глава 4. Простое разрушение потока Проблемы завершения, остановки и разрушения потоков
- •Досрочная остановка потока
- •Событие OnTerminate.
- •Контролируемая остановка потока - Подход 1.
- •Глава 5. Снова о разрушении потока. Тупик или зацикливание (Deadlock) Метод WaitFor
- •Контролируемое завершение потока - Подход 2
- •Введение в обработку сообщений и отложенное уведомление
- •WaitFor может вызвать долгую задержку
- •Вы заметили ошибку? WaitFor и Synchronize: зацикливание
- •Как избежать такого тупика
- •Глава 6. Снова о синхронизации: Критические секции и мьютексы Ограничения Synchronize
- •Критические секции
- •Что это все значит для Delphi-программиста?
- •На заметку
- •Могут ли данные пропасть или остаться недоступными в буфере?
- •Как насчет запоздавших сообщений (out of date)?
- •Проблемы Flow Control и неэффективность списка
- •Мьютексы
- •Глава 7. Программирование с использованием мьютексов. Управление конкуренцией Пора позаботиться о стиле?
- •Тупик из-за упорядочения мьютексов
- •Избавляемся от зацикливания потоков путем ожидания
- •Избавляемся от зацикливания, устанавливая упорядочение захвата мьютексов
- •Из огня да в полымя!
- •Избавляемся от зацикливания "ленивым способом", давая Win32 сделать это за нас
- •Атомарность составных операций - управление конкуренцией оптимистическим и пессимистическим образом
- •Оптимистическое управление
- •Пессимистическое управление
- •Избавляемся от недостатков в схеме блокировки
- •Еще не все ясно? Можно и попроще!
- •Глава 8. Потокобезопасные классы в Дельфи и приоритеты Для чего писать потокобезопасные классы?
- •Типы потокобезопасных классов.
- •Потокобезопасная инкапсуляция или наследники существующих классов
- •Классы управления потоками данных
- •Мониторы
- •Классы Interlock (взаимоблокировки)
- •Поддержка потоков в vcl
- •Руководство разработчика потокобезопасных классов
- •Управление приоритетами
- •Что такое приоритет? Как это делается в Win32
- •Какой приоритет дать моему потоку?
- •Глава 9. Семафоры. Управление потоками данных Взаимосвязь источник-приемник Семафоры
- •Счетчик больше единицы? "Не вполне критические" секции
- •Новое применение семафоров: управление потоками данных
- •Ограниченный буфер
- •Реализация ограниченного буфера в Delphi
- •Создание: Корректная инициализация счетчиков семафоров
- •Работа: правильные времена ожидания
- •Разрушение: Очистка
- •Разрушение: тонкости остаются
- •Доступ к дескрипторам синхронизации должен быть синхронизован!
- •Управление дескрипторами в Win32
- •Решение
- •Использование ограниченного буфера: пример
- •В завершение...
- •Глава 10. Ввод/вывод и потоки данных: от блокировки к асинхронности и обратно Отличия от потока vcl и разработка интерфейса I/o (ввода/вывода)
- •Реализация компонента преобразования блокировка-асинхронность
- •Добавление к ограниченному буферу операций просмотра
- •Создание двунаправленного ограниченного буфера
- •Детальное рассмотрение Блокировочно-Асинхронного Буфера (баб)
- •Создание баб
- •Разрушение баб
- •Пример программы с использованием баб
- •Мы достигли нашей цели!
- •Заметили утечку памяти?
- •Избавляемся от утечки
- •Проблемы просмотра
- •Промежуточный буфер.
- •Различные ограничения
- •Обратная сторона монеты: Потоковые буферы
- •Глава 11. Синхронизаторы и события (Events). Дополнительные механизмы синхронизации.
- •Как добиться оптимальной эффективности.
- •Простой mrews.
- •Важные моменты реализации
- •Пример использования простого mrews
- •Основные операции:
- •Введение в события (Events).
- •Моделирование событий с помощью семафоров.
- •Простой mrews с использованием событий.
- •Глава 12. Еще о возможностях синхронизации в Win32. Повышенная эффективность с помощью операций взаимоблокировки (interlocked)
- •Атомарность ниоткуда
- •Счетчики событий и секвенсоры
- •Реализация взаимного исключения
- •Ограниченный буфер с одним поставщиком данных и одним потребителем
- •Ограниченный буфер с произвольным числом поставщиков и потребителей
- •Другие возможности синхронизации Win32
- •Глава 13. Использование потоков совместно с bde, Исключениями и dll dll и многопроцессное программирование
- •Поток и пространство процесса. Однопоточная dll
- •Написание многопоточных dll
- •Подключение dll
- •Западня 1: Энкапсуляция функции точки входа в Delphi
- •Написание многопроцессорной dll
- •Глобально именованные объекты
- •Dll в деталях
- •Инициализация dll
- •Приложение с использованием dll
- •Западня 2: Контекст потока в фунциях точки входа
- •Обработка исключений
- •Глава 14. Проблемы, встретившаяся на практике, и их решение Проблема
- •Решение
- •Пайп dll и интерфейсные файлы
- •Потоки читателя и писателя
Простой mrews с использованием событий.
Управляющие структуры, требуемые для имитации события с использованием семафоров, подобны структурам, использованным в простом синхронизаторе. Таким образом, имеет смысл попытаться создать синхронизатор, используя события вместо семафоров. Это не слишком трудно:
unit EventSync;
{ Martin Harvey 5/6/2000 }
Interface
uses Windows;
type
TEventSynchronizer = class(TObject)
Private
FDataLock, FWriteLock: TRTLCriticalSection;
FReaders, FWriters: integer;
FNoReaders, FNoWriters: THandle;
Public
constructor Create;
destructor Destroy; override;
procedure StartRead;
procedure StartWrite;
procedure EndRead;
procedure EndWrite;
end;
implementation
constructor TEventSynchronizer.Create;
begin
inherited Create;
InitializeCriticalSection(FDataLock);
InitializeCriticalSection(FWriteLock);
FNoReaders := CreateEvent(nil, true, true, nil);
FNoWriters := CreateEvent(nil, true, true, nil);
end;
destructor TEventSynchronizer.Destroy;
begin
DeleteCriticalSection(FDataLock);
DeleteCriticalSection(FWriteLock);
CloseHandle(FNoReaders);
CloseHandle(FNoWriters);
inherited Destroy;
end;
procedure TEventSynchronizer.StartRead;
var
Block: boolean;
Begin
EnterCriticalSection(FDatalock);
if FReaders = 0 then
ResetEvent(FNoReaders);
Inc(FReaders);
Block := FWriters > 0;
LeaveCriticalSection(FDataLock);
if Block then
WaitFor
end;
Глава 12. Еще о возможностях синхронизации в Win32. Повышенная эффективность с помощью операций взаимоблокировки (interlocked)
Стандартные примитивы синхронизации могут ввести излишние ограничения для простых многопоточных систем, особенно для потоков, которые интенсивно синхронизируются друг с другом. Одна из возможных альтернатив - использование interlocked операций.
Interlocked (взаимосвязанные, взаимоблокировочные) операции первоначально были задуманы как механизм синхронизации низкого уровня для симметричных многопроцессорных систем с разделяемой памятью. Во многопроцессорных системах общая память представляет собой чрезвычайно эффективный путь обмена данными между процессами и потоками. Необходимо обеспечить методы решения проблем атомарности, когда два или более процессоров пытаются использовать один и тот же участок памяти. Почти все современные процессоры поддерживают соответствующие операции взаимоблокировки. Эти такие операции, посредством которыхо процессор может прочитать значение из памяти, модифицировать его, а затем записать атомарно, причем гарантируется, что другие процессоры не получит доступ к тому же участку памяти, а процессор, выполняющий операцию, не будет прерван. Win32 обеспечивает следующие операции взаимоблокировки:
InterlockedCompareExchange (только Win NT/2K).
InterlockedDecrement.
InterlockedExchange.
InterlockedExchangeAdd (только Win NT/2K).
InterlockedIncrement.
Так почему бы не использовать такие операции всегда? Хорошим примером может служить замок (spin lock). Допустим, есть желание создать что-то подобное критической секции. Однако в критической секции может быть очень мало кода, а доступ к нему нужен очень часто. В таких случаях, полноценный объект синхронизации может оказаться излишним. Spin lock (замок) позволяет нам добиться аналогичного результата, и работает примерно так
Var
Lock:integer;
{Lock Initialization}
Lock := -1;
{Entering the spin lock}
while InterlockedIncrement(Lock)>0 do
begin
Dec(Lock);
Sleep(0);
end;
{Leaving the spin lock}
Dec(Lock);
Поток захватывает замок, если при выполнении приращения InterlockedIncrement значение Lock будет нулевым. Если эта величина больше нуля, то замок захвачен другим потоком, и требуется новая попытка. Вызов Sleep нужен, чтобы один поток не ждал впустую слишком долго, пока поток с более низким приоритетом удерживает замок. Для простых планировщиков, если приоритеты потоков равны, то вызов Sleep может и не потребоваться. Операция блокировки необходима, поскольку если поток выполняет чтение значения из памяти, увеличение его, сравнение, а затем записывает назад, то два потока могут захватить замок одновременно.
Излишние действия почти исключены, поскольку лишь несколько инструкций CPU требуется для входа и выхода из замка, а потоку не приходится ждать. Если потокам нужно ожидать существенное время, то процессор работает впустую, так что это подходит только для создания небольших критических секций. Замки полезны для реализации критических секций, которые сами являются частью структур синхронизации. Коллективные данные внутри примитивов синхронизации или планировщиков часто защищены блокировкой подобного типа: часто это необходимо, поскольку примитивы синхронизации уровня OС не могут быть использованы, чтобы осуществлять примитивы синхронизации уровня OС. У такой блокировки имеются те же проблемы с конкуренцией потоков, как и у мьютексов, но отличие состоит в том, что зацикливание происходит не путем замораживания (deadlock), а динамически (livelock). Это несколько худшая ситуация, потому что хотя "блокированные" потоки не выполняют полезного кода, они работают в бесконечном цикле, расходуя время процессора и понижая производительность всей системы. Замки нельзя использовать как семафоры, чтобы "приостанавливать" потоки.
