завершить запущенный поток, поскольку это может вызвать разнообразные проб-
лемы в коде и, возможно, даже в пространственно-временном континууме.
Потоки могут быть опасны. Как и управление памятью вручную в языках вроде
С и С++, они могут вызвать появление ошибок, которые ужасно трудно найти
и исправить. Для того чтобы использовать потоки, весь код программы — и код
внешних библиотек, которые он использует, — должен быть потокобезопасным.
В предыдущем примере кода потоки не работали с глобальными переменными,
поэтому они могли работать независимо, ничего не разрушая.
Представьте, что вы исследуете паранормальную активность в доме с привиде-
ниями. Привидения скитаются по коридорам, но ни одно из них не знает о другом,
и в любой момент любое из них может просмотреть, добавить, удалить или пере-
местить домашнюю обстановку.
Вы настороженно идете по дому, снимая показатели со своих впечатляющих
инструментов. И внезапно замечаете, что подсвечник, мимо которого прошли не-
сколько секунд назад, пропал.
Содержимое дома похоже на переменные программы. Привидения — это по-
токи процесса (дома). Если бы привидения только просматривали содержимое
дома, проблемы бы не было — поток просто считает значение константы или пере-
менной, не пытаясь его изменить.
Однако некая невидимая сущность может схватить ваш фонарик, дунуть холод-
ной струей воздуха на вашу шею, рассыпать шарики на ступеньках или заставить
вспыхнуть огонь в камине. Особо утонченные привидения изменили бы что-нибудь
в другой комнате, чего вы бы даже не заметили.
Несмотря на ваши шикарные инструменты, вам будет очень трудно разобрать-
ся в том, кто, как и когда это сделал.
Если бы вы использовали несколько процессов вместо потоков, это было бы
похоже на несколько домов, в которых обитает только одно (живое) существо. Если
бы вы поставили бутылку бренди перед камином, она все еще была бы на своем
месте час спустя. Возможно, немного жидкости испарилось бы, но сама бутылка
осталась бы на том же месте.
Потоки могут быть полезны и безопасны, когда речь не идет о глобальных дан-
ных. В частности, потоки полезно использовать для экономии времени при
ожидании завершения некой операции ввода/вывода. В этих случаях потокам
308
Глава 11. Конкуренция и сети
не придется сражаться за данные, поскольку у каждого из них имеется свой набор
переменных.
Но потоки иногда могут менять глобальные данные по хорошей причине.
Фактически самая распространенная причина использования нескольких пото-
ков — это возможность разделить между ними работу над некоторыми данными,
поэтому можно ожидать, что некоторые данные будут изменены.
Классический способ разделить данные безопасно — разместить программную
блокировку перед изменением переменной в потоке. Это позволит оградить ее
значение от других потоков и внести свои изменения. В примере с домом вы бы
просто оставили бригаду охотников за привидениями в той комнате, которая долж-
на остаться свободной от привидений. Вам лишь нужно не забывать разблокировать
ее. Блокировки также могут быть вложенными — что, если другая бригада охотни-
ков за привидениями также будет наблюдать за этой же комнатой или за всем до-
мом? Использование блокировок является традицией, но печально известно тем,