Выводы по гибридным конструкциям
Я рекомендую по возможности избегать кода, блокирующего потоки. Выполняя
асинхронные вычисления или операции ввода-вывода, передавайте данные от
одного потока к другому так, чтобы исключить одновременную попытку доступа
к данным со стороны нескольких потоков. Если это невозможно, используйте
методы классов
Volatile
и
Interlocked
, так как они работают быстро и не блоки-
руют потоки. К сожалению, они подходят только для работы с простыми типами.
Впрочем, даже в этом случае вы можете выполнять достаточно сложные операции,
описанные в предыдущей главе.
Две основные причины, по которым приходится блокировать потоки:
Упрощение модели программирования.
Блокируя поток, вы жертвуете ресур-
сами и производительностью, но получаете возможность писать код последо-
вательно, не прибегая к методам обратного вызова. Асинхронные функции C#
предоставляют упрощенную модель программирования без необходимости
блокирования потоков.
Поток имеет определенное назначение.
Некоторые потоки используются для
решения конкретных задач. Лучший пример такого рода — основной поток
приложения. Без блокировки он рано или поздно вернет управление, и процесс
завершится. Другим примером является поток или потоки графического интер-
фейса приложения. Windows требует, чтобы манипуляции окнами и элементами
управления осуществлял только породивший их поток. Поэтому периодически
приходится писать код, блокирующий GUI-поток до завершения каких-то дру-
гих операций. И только после этого данный поток обновляет окна и элементы
874
Глава.30 .Гибридные.конструкции.синхронизации.потоков
управления. Разумеется, блокировка GUI-потока подвешивает приложение
и мешает работе пользователя.
Чтобы избежать блокировки потоков, не стоит мысленно связывать их с кон-
кретными операциями. К примеру, не нужно создавать поток, проверяющий орфо-
графию, поток, проверяющий грамматику, поток, обрабатывающий определенные
клиентские запросы и т. п. Потому что подобный подход как бы показывает, что
поток не может выполнять других операций. Однако потоки являются слишком
ценным ресурсом, чтобы ограничивать их только одной операцией. Используйте
пул для аренды потоков на короткий период времени. Поток из пула может начать
с проверки орфографии, затем перейти к проверке грамматики, затем заняться об-
работкой клиентских запросов и т. п.
Если же вы, несмотря ни на что, решите блокировать потоки, для синхронизации
потоков из разных доменов или процессов используйте конструкции режима ядра.
Для атомарного управления состоянием через набор операций вам потребуется класс
Monitor
с закрытым полем
1
. В качестве альтернативы можно прибегнуть к блоки-
рованию на чтение-запись. В общем случае это блокирование работает медленнее
класса
Monitor
, но оно позволяет одновременно исполняться нескольким потокам
чтения, что повышает общую производительность и минимизирует возможность
блокировки потоков.
Также старайтесь избегать рекурсивных блокировок (особенно блокировок
чтения-записи), так как они серьезно снижают производительность. Впрочем,
класс
Monitor
, несмотря на свою рекурсивность, показывает высокую произво-
дительность
2
. Кроме того, старайтесь не снимать блокировку в блоке
finally
, так
как вход в блоки обработки исключений и выход из них негативно сказывается на
производительности. Кроме того, выдача исключения при изменении состояния
приводит к ситуации, когда другим потокам приходится работать с поврежденными
данными, из-за чего результат работы приложения становится непредсказуемым
и возникают бреши в системе безопасности.
И, разумеется, если ваш код все-таки удерживает блокировку, он не должен
удерживать ее слишком долго, так как в результате повышается вероятность бло-
кирования потоков. Далее будет показана техника работы с классами коллекций,
позволяющая избежать длительного удерживания блокировки.
Ну и, наконец, для вычислительных операций можно использовать задания (о ко-
торых мы говорили в главе 27), чтобы обойтись без многочисленных конструкций
синхронизации. В частности, мне нравится, что с каждым заданием можно связать
1
Вместо класса Monitor можно воспользоваться чуть более быстрым классом SpinLock.
Но при этом возможна ситуация, когда ресурсы процессора начнут тратиться впустую. И с
моей точки зрения, класс SpinLock не настолько превосходит Monitor по скорости, чтобы
оправдать его применение.
2
Частично это связано с тем, что класс Monitor реализован на машинном, а не на управ-
ляемом коде.
875
Блокировка.с.двойной.проверкой
одно или несколько других заданий, которые начинают выполняться средствами
пула потоков при завершении некой операции. Это намного лучше, чем блокировать
поток, ожидая завершения операции. Для операций ввода-вывода следует вызывать
различные методы
XxxAsync
, которые заставляют ваш код продолжать выполнение
после завершений операций ввода-вывода (аналог заданий, выполняемых по за-
вершению операции).
Do'stlaringiz bilan baham: |