
- •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 и интерфейсные файлы
- •Потоки читателя и писателя
Атомарность ниоткуда
При достаточной аккуратности возможно создать замок, который является атомарным, вообще не прибегая к взаимоблокировке, при условии, что прерывания будут происходить только между инструкциями CPU. Рассмотрим код
procedure DoSomethingCritical(var Lock: integer);
var
Temp: integer;
Begin
{ Initialize lock }
Lock := -1;
{ Enter Lock }
Repeat
Inc(Lock);
Temp := Lock;
if Temp > 0 then
Dec(Lock);
until not (Temp > 0);
{ Perform operations }
{ Leave Lock }
Dec(Lock);
end;
procedure AsmDoSomethingCritical(var Lock: integer);
asm
{ Initialize lock }
lock mov dword ptr[eax],$FFFFFFFF
{ Enter Lock }
@spin:
lock inc dword ptr[eax]
mov edx,[eax]
test edx,edx
jng @skipdec
lock dec dword ptr[eax]
@skipdec:
test edx,edx
jg @spin
{ Perform operations }
{ Leave Lock }
lock dec dword ptr[eax]
end;
Сначала обратим внимание на код на Паскале, чтобы понять основную идею. У нас есть замок - целое число в памяти. При попытке войти в замок мы сначала увеличиваем значение в памяти. Затем читаем значение из памяти в локальную переменную, и проверяем, как и раньше, больше ли оно нуля. Если это так, то кто-то еще обладает этим замком, и мы возвращаемся к началу, в противном случае мы захватываем замок.
Самое важное в этом наборе операций то, что при определенных условиях переключение потоков может произойти в любой момент времени, но потокобезопасность все же сохранится. Первое приращение замка является косвенным приращением регистра. Значение всегда находится в памяти, и приращение атомарно. Затем мы читаем значение замка в локальную переменную. Это действие не атомарное. Значение, прочитанное в локальную переменную, может отличаться от результата приращения. Тем не менее, хитрость состоит в том, что поскольку приращение выполняется перед действием чтения, происходящие конфликты потоков всегда будут приводить к слишком высокому прочитанному значению, а не к слишком низкому: в результате конфликтов потоков можно узнать, свободен ли замок.
Часто полезно писать подобные действия на ассемблере, чтобы быть полностью уверенным, что правильные значения останутся в памяти, а не кешируются в регистрах. Компилятор Delphi (по крайней мере Delphi 4), при передаче замка как var-параметра, и с использованием локальной переменной, генерирует корректный код, который будет работать на однопроцессорных машинах. На многопроцессорных машинах косвенные приращения и декременты регистра не атомарны. Эта проблема решена в ассемблерной версии кода путем добавления префикса lock перед инструкциями, которые имеют дело с замком. Этот префикс указывает процессору исключительно заблокировать шину памяти на все время выполнения инструкции, таким образом этими операции становятся атомарными.
Плохо только, что хотя это и теоретически правильно, виртуальная машина Win32 не позволяет процессам пользовательского уровня исполнять инструкции с префиксом lock. Программисты, предполагающие действительно применять этот механизм, должны использовать его только в коде с привилегиями нулевого кольца (ring 0). Другая проблема состоит в том, что поскольку эта версия блокировки не вызывает Sleep, потоки способны монополизировать процессор, пока они ожидают снятия блокировки, а это гарантирует зависание машины.