thread
-
Имя
- thread — Расширение для доступа скрипта к потокам Tcl
- Синтаксис
- Описание
- Команды
-
thread::create ?-joinable? ?-preserved? ?script?
- thread::preserve ?id?
- thread::release ?-wait? ?id?
- thread::id
- thread::errorproc ?procname?
- thread::cancel ?-unwind? id ?result?
- thread::unwind
- thread::exit ?status?
- thread::names
- thread::exists id
- thread::send ?-async? ?-head? id script ?varname?
- thread::broadcast script
- thread::wait
- thread::eval ?-lock mutex? arg ?arg . . .?
- thread::join id
- thread::configure id ?option? ?value? ? . . .?
- thread::transfer id channel
- thread::detach channel
- thread::attach channel
- thread::mutex
-
thread::mutex create ?-recursive?
- thread::mutex destroy mutex
- thread::mutex lock mutex
- thread::mutex unlock mutex
- thread::mutex destroy mutex
- thread::rwmutex
-
thread::rwmutex create
- thread::rwmutex destroy mutex
- thread::rwmutex rlock mutex
- thread::rwmutex wlock mutex
- thread::rwmutex unlock mutex
- thread::rwmutex destroy mutex
- thread::cond
-
thread::cond create
- thread::cond destroy cond
- thread::cond notify cond
- thread::cond wait cond mutex ?ms?
- thread::cond destroy cond
Имя
thread — Расширение для доступа скрипта к потокам TclСинтаксис
пакет требуется Tcl 8 .4пакет требуется Thread ?2 .8?
thread::create ?-joinable? ?-preserved? ?script?
thread::preserve ?id?
thread::release ?-wait? ?id?
thread::id
thread::errorproc ?procname?
thread::cancel ?-unwind? id ?result?
thread::unwind
thread::exit ?status?
thread::names
thread::existsid
thread::send ?-async? ?-head? idscript ?varname?
thread::broadcastscript
thread::wait
thread::eval ?-lock mutex? arg ?arg . . .?
thread::joinid
thread::configureid ?option? ?value? ? . . .?
thread::transferidchannel
thread::detachchannel
thread::attachchannel
thread::mutex
thread::mutexcreate ?-recursive?
thread::mutexdestroymutex
thread::mutexlockmutex
thread::mutexunlockmutex
thread::rwmutex
thread::rwmutexcreate
thread::rwmutexdestroymutex
thread::rwmutexrlockmutex
thread::rwmutexwlockmutex
thread::rwmutexunlockmutex
thread::cond
thread::condcreate
thread::conddestroycond
thread::condnotifycond
thread::condwaitcondmutex ?ms?
Описание
Расширение thread создает потоки, содержащие интерпретаторы Tcl, и позволяет отправлять скрипты в эти потоки для оценки. Кроме того, оно предоставляет доступ на уровне скриптов к основным примитивам синхронизации потоков, таким как мьютексы и переменные состояния.Команды
Этот раздел описывает команды для создания и уничтожения потоков, а также для отправки скриптов в потоки для их оценки.- thread::create ?-joinable? ?-preserved? ?script?
- Эта команда создаёт поток, содержащий интерпретатор Tcl. Интерпретатор Tcl либо выполняет необязательный параметр script, если он указан, либо ожидает в цикле событий скрипты, которые поступают через команду thread::send. Результат, если таковой имеется, необязательного параметра script никогда не возвращается вызывающей стороне. Результатом thread::create является идентификатор потока. Это неявная ссылка, которая идентифицирует вновь созданный поток для всех других команд пакета. Ссылка на поток выходит из области видимости автоматически, когда поток помечен на выход (см. команду thread::release ниже).
Если необязательный параметр script содержит команду thread::wait, поток войдёт в цикл событий. Если такая команда не найдена в script, поток выполнит script до конца и завершится. В этом случае ссылка может быть безопасно проигнорирована, так как она ссылается на поток, который больше не существует к моменту возвращения команды.
Используя флаг -joinable, можно создать присоединяемый поток, т.е. поток, на завершение которого можно дождаться с помощью команды thread::join. Обратите внимание, что отсутствие присоединения потока, созданного с флагом -joinable, приводит к утечкам ресурсов и памяти.
Потоки, созданные командой thread::create, не могут быть уничтожены принудительно. Следовательно, нет соответствующей команды уничтожения потока. Поток может быть только освобождён с помощью команды thread::release, и если его внутренний счётчик ссылок станет равным нулю, поток будет помечен на выход. Это выводит поток из цикла обслуживания событий, и поток продолжает выполнение команд, переданных в параметре script, следуя команде thread::wait. Если это была последняя команда в скрипте, как это обычно бывает, поток завершится.
Возможно создание ситуации, в которой невозможно завершить поток, например, путём размещения бесконечного цикла после thread::wait или повторного входа в цикл событий с помощью команды типа vwait. В таких случаях поток может никогда не завершиться. Это считается плохой практикой и должно быть избегаемо, если это возможно. Это лучше всего иллюстрируется примером ниже:
# You should never do . . . set tid [thread::create { package require Http thread::wait vwait forever ; # <-- this! }] - Поток, созданный в приведённом выше примере, никогда не сможет завершиться. После освобождения его с последним соответствующим вызовом thread::release, поток выскочит из thread::wait и продолжит выполнение следующих команд. Он войдёт в команду vwait и будет бесконечно ждать событий. Нет способа завершить такой поток, поэтому вы не должны этого делать!
Каждый вновь созданный поток имеет свой внутренний счётчик ссылок, установленный в 0 (ноль), т.е. он не зарезервирован. Этот счётчик увеличивается при вызове thread::preserve и уменьшается при вызове команды thread::release. Эти две команды реализуют простую, но эффективную систему резервирования потоков и предлагают предсказуемые и управляемые возможности завершения потоков. Тем не менее, возможно создание изначально зарезервированных потоков с помощью флага -preserved команды thread::create. Потоки, созданные с этим флагом, имеют начальное значение счётчика ссылок 1 (один) и, следовательно, изначально помечены как зарезервированные.
- thread::preserve ?id?
- Эта команда увеличивает счётчик ссылок потока. Каждый вызов этой команды увеличивает счётчик ссылок на единицу (1). Команда возвращает значение счётчика ссылок после увеличения. Если она вызвана с необязательным параметром id потока, команда сохраняет указанный поток. В противном случае сохраняется текущий поток.
С помощью счётчика ссылок можно реализовать контролируемый доступ к общему потоку Tcl. Увеличивая счётчик ссылок, вызывающая сторона сигнализирует о желании использовать поток в течение более длительного времени. Уменьшая счётчик, вызывающая сторона сигнализирует о завершении использования потока.
- thread::release ?-wait? ?id?
- Эта команда уменьшает счётчик ссылок потока. Каждый вызов этой команды уменьшает счётчик ссылок на единицу (1). Если она вызвана с необязательным параметром id потока, команда освобождает указанный поток. В противном случае освобождается текущий поток. Команда возвращает значение счётчика ссылок после уменьшения. Когда счётчик ссылок достигает нуля (0), целевой поток помечается для завершения. Вы не должны ссылаться на поток после того, как команда thread::release вернула ноль или отрицательное число. Ссылка на поток выходит из области видимости и больше не должна использоваться. Любая последующая ссылка на тот же идентификатор потока приведёт к ошибке Tcl.
Необязательный флаг -wait инструктирует вызывающий поток подождать завершения целевого потока, если результат команды приведёт к завершению целевого потока, т.е. если возвращаемое значение будет равно нулю (0). Без флага вызывающий поток не ждёт завершения целевого потока. Следует проявлять осторожность при использовании -wait, так как это может блокировать вызывающий поток на неопределённое время. Этот параметр был реализован для некоторых специальных случаев использования расширения и устарел для обычного использования. Пользователи по умолчанию должны создавать присоединяемые потоки, используя параметр -joinable команды thread::create и thread::join, чтобы дождаться завершения потока.
- thread::id
- Эта команда возвращает идентификатор текущего потока.
- thread::errorproc ?procname?
- Эта команда устанавливает обработчик ошибок, которые возникают в скриптах, отправленных асинхронно с помощью флага -async команды thread::send в другие потоки. Если обработчик не указан, возвращается текущий обработчик. Пустая строка сбрасывает обработчик до значения по умолчанию (неуказанного). Неперехваченная ошибка в потоке приводит к сообщению об ошибке, отправленному в стандартный канал ошибок. Этот стандартный способ сообщения об ошибке может быть изменён путём регистрации процедуры, которая вызывается для отчёта об ошибке. procname вызывается в интерпретаторе, который вызвал команду thread::errorproc. procname вызывается так:
myerrorproc thread_id errorInfo
- thread::cancel ?-unwind? id ?result?
- Для этой команды требуется версия Tcl 8.6 или выше.
Отменяет скрипт, выполняемый в потоке, заданном параметром id. Без переключателя -unwind стек выполнения для интерпретатора разворачивается до тех пор, пока не будет найдена окружающая команда catch, или не останется больше вызовов интерпретатора в стеке вызовов. С переключателем -unwind стек выполнения для интерпретатора разворачивается без учёта каких-либо промежуточных команд catch до тех пор, пока не останется больше вызовов интерпретатора в стеке вызовов. Если result присутствует, он будет использован в качестве строки сообщения об ошибке; в противном случае будет использована строка сообщения об ошибке по умолчанию.
- thread::unwind
- Использование этой команды устарело в пользу более продвинутой системы резервирования потоков, реализованной с помощью команд thread::preserve и thread::release. Поддержка команды thread::unwind исчезнет в будущей основной версии расширения.
Эта команда останавливает предыдущую команду thread::wait. Выполнение скрипта, переданного вновь созданному потоку, продолжится с команды thread::wait. Если thread::wait была последней командой в скрипте, поток завершится. Команда возвращает пустой результат, но в некоторых ситуациях может вызвать ошибку Tcl с сообщением "целевой поток умер".
- thread::exit ?status?
- Использование этой команды устарело в пользу более продвинутой системы резервирования потоков, реализованной с помощью команд thread::preserve и thread::release. Поддержка команды thread::exit исчезнет в будущей основной версии расширения.
Эта команда принудительно завершает поток, застрявший в команде thread::wait. Статус выхода потока по умолчанию равен 666 и может быть задан с помощью необязательного параметра status. Выполнение команды thread::exit гарантирует оставление памяти программы в несогласованном состоянии, создаст утечки памяти и повлияет на другие подсистемы приложения Tcl непредсказуемым образом. Команда возвращает пустой результат, но в некоторых ситуациях может вызвать ошибку Tcl с сообщением "целевой поток умер".
- thread::names
- Эта команда возвращает список идентификаторов потоков. Они относятся только к потокам, созданным с помощью команды thread::create. Если ваше приложение создаёт другие потоки на уровне C, они не будут отражены этой командой.
- thread::exists id
- Возвращает true (1), если поток с заданным параметром id существует, false (0) в противном случае. Это относится только к потокам, созданным с помощью команды thread::create.
- thread::send ?-async? ?-head? id script ?varname?
- Эта команда передаёт script другому потоку и, необязательно, ждёт результата. Если указан флаг -async, команда не ждёт результата и возвращает пустую строку. Целевой поток должен войти в свой цикл событий, чтобы получить скрипты, отправленные с помощью этой команды. Это делается по умолчанию для потоков, созданных без начального скрипта. Потоки могут явно войти в цикл событий, вызвав thread::wait или любую другую соответствующую команду Tcl/Tk, например update, vwait и т.д.
Необязательный параметр varname указывает имя переменной для хранения результата script. Без флага -async команда возвращает код выполнения, аналогично стандартной команде Tcl catch. Однако, если указан флаг -async, команда возвращает значение немедленно, и вызывающая сторона может позже использовать vwait на ?varname?, чтобы получить результат переданного script
set t1 [thread::create] set t2 [thread::create] thread::send -async $t1 "set a 1" result thread::send -async $t2 "set b 2" result for {set i 0} {$i < 2} {incr i} { vwait result } - В приведённом выше примере два потока выполняли работу, и обоим было дано указание сигнализировать об одной и той же переменной "result" в вызывающем потоке. Вызывающая сторона дважды вошла в цикл событий, чтобы получить оба результата. Однако порядок получения результатов может меняться в зависимости от текущей загрузки системы, типа выполняемой работы и т.д.
Несколько потоков могут одновременно отправлять скрипты для выполнения в целевой поток. Все они попадают в очередь событий целевого потока и выполняются в порядке очереди FIFO, перемежаясь с другими событиями, ожидающимися в очереди событий целевого потока. С помощью необязательного переключателя ?-head?, скрипты, помещённые в очередь событий потока, могут быть размещены в начале, а не в конце очереди, выполняясь таким образом в порядке LIFO.
- thread::broadcast script
- Эта команда передаёт script для выполнения всем потокам, созданным пакетом. Она не ждёт ответа ни от одного из потоков.
- thread::wait
- Это вводит цикл обработки событий, чтобы поток мог получать сообщения от команды thread::send. Данная команда должна использоваться только в скрипте, переданном в команду thread::create. Она должна быть последней командой в скрипте. Если это не так, выходящий поток продолжит выполнение строк скрипта после thread::wait, что обычно не нужно и/или не ожидается.
set t1 [thread::create { # # Do some initialization work here # thread::wait ; # Enter the event loop }] - thread::eval ?-lock mutex? arg ?arg . . .?
- Эта команда конкатенирует переданные аргументы и оценивает результирующий скрипт под защитой мьютекса. Если мьютекс не указан с помощью необязательного аргумента ?-lock mutex?, используется внутренний статический мьютекс.
- thread::join id
- Эта команда ожидает завершения потока с ID id и возвращает его код выхода. Ошибки будут возвращены для потоков, которые не могут быть объединены или уже ожидают другого потока. После объединения дескриптор потока выходит из области видимости и больше не должен использоваться.
- thread::configure id ?option? ?value? ? . . .?
- Эта команда настраивает различные низкоуровневые аспекты потока с ID id аналогичным образом, как стандартная команда Tcl fconfigure настраивает некоторые опции канала Tcl. В настоящее время поддерживаются следующие опции: -eventmark и -unwindonerror.
Опция -eventmark, при установке, ограничивает количество асинхронно помещенных скриптов в цикл обработки событий потока. Команда thread::send -async будет блокироваться, пока количество ожидающих скриптов в цикле обработки не уменьшится до значения, настроенного с помощью -eventmark. Значение по умолчанию для -eventmark равно 0 (ноль), что фактически отключает проверку, т.е. допускает неограниченное количество помещенных скриптов.
Опция -unwindonerror, при установке, заставляет целевой поток разворачиваться, если обработка скрипта привела к ошибке. Значение по умолчанию для -unwindonerror равно 0 (ложь), т.е. поток продолжает обрабатывать скрипты после того, как один из помещенных скриптов завершился ошибкой.
- thread::transfer id channel
- Это перемещает указанный канал из текущего потока и интерпретатора в основной интерпретатор потока с заданным id. После перемещения текущий интерпретатор больше не имеет доступа к каналу, но основной интерпретатор целевого потока сможет использовать его. Команда ожидает, пока другой поток не включит канал. Из-за этого возможно возникновение тупика участвующих потоков, приказав другому потоку через синхронный thread::send переместить канал к нам. Это легко расширяется до более длинных циклов ожидания потоков друг друга. Другие ограничения: канал не должен быть общим для нескольких интерпретаторов, работающих в отправляющем потоке. Это автоматически исключает специальные каналы для стандартного ввода, вывода и ошибок.
Из-за внутренней реализации ядра Tcl и ограничения на передачу общих каналов необходимо принять дополнительные меры при передаче каналов сокетов, созданных путем приема соединения из процедур обратного вызова команд socket:
socket -server _Accept 2200 proc _Accept {s ipaddr port} { after idle [list Accept $s $ipaddr $port] } proc Accept {s ipaddr port} { set tid [thread::create] thread::transfer $tid $s } - thread::detach channel
- Это отсоединяет указанный канал от текущего потока и интерпретатора. После этого текущий интерпретатор больше не имеет доступа к каналу. Канал находится в приостановленном состоянии, пока какой-либо другой (или тот же) поток не подключит канал снова с помощью thread::attach. Ограничения: такие же, как и для передачи общих каналов с помощью команды thread::transfer.
- thread::attach channel
- Это подключает ранее отсоединенный канал в текущем потоке/интерпретаторе. Для уже существующих каналов команда ничего не делает, т.е. подключение одного и того же канала более одного раза не является ошибкой. Первая операция фактически выполнит операцию, а все последующие операции просто ничего не сделают. Команда генерирует ошибку, если канал не найден в списке отсоединенных каналов и/или в текущем интерпретаторе.
- thread::mutex
- Мьютексы — самые распространённые примитивы синхронизации потоков. Они используются для синхронизации доступа от двух или более потоков к одному или нескольким общим ресурсам. Эта команда предоставляет доступ на уровне скрипта к эксклюзивным и/или рекурсивным мьютексам. Эксклюзивные мьютексы могут быть заблокированы только один раз одним потоком, а рекурсивные мьютексы могут быть заблокированы многократно тем же потоком. Для рекурсивных мьютексов количество операций блокировки и разблокировки должно совпадать, в противном случае мьютекс никогда не будет разблокирован, что приведёт к различным ситуациям тупика.
Необходимо проявлять осторожность при использовании мьютексов в многопоточной программе. Неправильное использование мьютексов может привести к различным ситуациям тупика, особенно при использовании эксклюзивных мьютексов.
Команда thread::mutex поддерживает следующие подкоманды и опции:
- thread::mutex create ?-recursive?
- Создаёт мьютекс и возвращает его неявный дескриптор. Этот дескриптор должен использоваться для всех будущих ссылок на вновь созданный мьютекс. Если необязательный аргумент ?-recursive? не был указан, команда создаёт эксклюзивный мьютекс. С аргументом ?-recursive? команда создаёт рекурсивный мьютекс.
- thread::mutex destroy mutex
- Уничтожает mutex. Мьютекс должен быть разблокирован перед попыткой уничтожения. Если мьютекс заблокирован, команда выбросит ошибку Tcl.
- thread::mutex lock mutex
- Захватывает блокировку mutex. Блокировка эксклюзивного мьютекса может выбросить ошибку Tcl, если по попытке захватить блокировку того же мьютекса дважды одним и тем же потоком. Если логика вашей программы вынуждает вас захватывать блокировку того же мьютекса дважды или более от одного и того же потока (это может произойти при рекурсивных вызовах процедур), следует рассмотреть использование рекурсивных мьютексов.
- thread::mutex unlock mutex
- Разблокирует mutex, чтобы какой-либо другой поток мог его захватить. Попытка разблокировать уже разблокированный мьютекс вызовет ошибку Tcl.
- thread::rwmutex
- Эта команда создаёт мьютексы с множеством читателей/одним писателем. Мьютексы читатель/писатель позволяют более оптимально сериализовать доступ к общему ресурсу. В ситуациях, когда общий ресурс в основном читается и редко изменяется, вы можете получить некоторое повышение производительности, используя мьютексы читатель/писатель вместо эксклюзивных или рекурсивных мьютексов.
Для чтения ресурса поток должен получить разрешение на чтение ресурса. Разрешение на чтение не является эксклюзивным, что означает, что более одного потока могут получить разрешение на чтение того же ресурса без ожидания других читателей. Однако для изменения ресурса поток должен получить эксклюзивное разрешение на запись. Это разрешение эффективно блокирует все потоки от получения разрешения на чтение, пока ресурс изменяется потоком-писателем. Только после освобождения разрешения на запись ресурс снова может быть заблокирован на чтение.
Команда thread::rwmutex поддерживает следующие подкоманды и опции:
- thread::rwmutex create
- Создаёт мьютекс читатель/писатель и возвращает его неявный дескриптор. Этот дескриптор должен использоваться для всех будущих ссылок на вновь созданный мьютекс.
- thread::rwmutex destroy mutex
- Уничтожает мьютекс читатель/писатель mutex. Если мьютекс уже заблокирован, попытка уничтожить его вызовет ошибку Tcl.
- thread::rwmutex rlock mutex
- Захватывает блокировку mutex для чтения. Более чем один поток может одновременно захватить блокировку на чтение того же mutex.
- thread::rwmutex wlock mutex
- Захватывает блокировку mutex для записи. Только один поток может одновременно захватить блокировку на запись того же mutex. Попытка захватить блокировку на запись того же mutex дважды одним и тем же потоком вызовет ошибку Tcl.
- thread::rwmutex unlock mutex
- Разблокирует mutex, чтобы какой-либо другой поток мог его захватить. Попытка разблокировать уже разблокированный mutex вызовет ошибку Tcl.
- thread::cond
- Эта команда предоставляет доступ на уровне скрипта к переменным состояния. Переменная состояния создаёт безопасную среду для программы для проверки какого-либо условия, сна сна, когда условие ложно, и пробуждения, когда оно, возможно, стало истинным. Переменная состояния всегда используется в сочетании с эксклюзивным мьютексом. Если вы попытаетесь использовать другой тип мьютекса в сочетании с переменной состояния, будет выброшено сообщение об ошибке Tcl.
Команда поддерживает следующие подкоманды и опции:
- thread::cond create
- Создаёт переменную состояния и возвращает её неявный дескриптор. Этот дескриптор должен использоваться для всех будущих ссылок на вновь созданную переменную состояния.
- thread::cond destroy cond
- Уничтожает переменную состояния cond. Следует проявлять особую осторожность, чтобы никто не использовал (т.е. не ожидал) переменную состояния, в противном случае могут возникнуть непредвиденные ошибки.
- thread::cond notify cond
- Разбуживает все потоки, ожидающие переменной состояния cond.
- thread::cond wait cond mutex ?ms?
- Эта команда используется для приостановки выполнения программы до тех пор, пока переменная состояния cond не будет сигнализирована или не истечёт время ожидания (опционально). Эксклюзивный mutex должен быть захвачен вызывающим потоком при входе в эту команду. Если мьютекс не захвачен, выводится ошибка Tcl. Во время ожидания cond команда освобождает mutex. Перед возвратом в вызывающий поток команда снова получает mutex. Разблокирование mutex и ожидание переменной состояния cond выполняются атомарно.
Опция ms, если указана, должна быть целым числом, определяющим интервал времени в миллисекундах, в течение которого команда ожидает сигнализации. В противном случае команда ожидает сигнала уведомления вечно.
В многопоточных программах часто возникают ситуации, когда поток должен ожидать какого-либо события, пока ему не будет разрешено продолжить выполнение. Обычно это достигается путём многократной проверки условия под защитой мьютекса и ожидания переменной состояния до тех пор, пока условие не станет истинным:
set mutex [thread::mutex create] set cond [thread::cond create] thread::mutex lock $mutex while {<some_condition_is_true>} { thread::cond wait $cond $mutex } # Do some work under mutex protection thread::mutex unlock $mutex - Повторная проверка условия необходима, так как переменная состояния может быть сигнализирована без фактического изменения условия (например, ложные пробуждения потоков).
Обсуждение
Основная модель потоков в Tcl заключается в том, что может быть один или несколько интерпретаторов Tcl на поток, но каждый интерпретатор Tcl должен использоваться только одним потоком, который его создал. Абстракция «общая память» неудобна для предоставления в Tcl, так как Tcl делает предположения о владении переменными и данными. Поэтому это расширение поддерживает простую форму многопоточности, где основной поток может управлять несколькими фоновыми, или «рабочими» потоками. Например, сервер с обработкой событий может передавать запросы рабочим потокам, а затем ожидать ответы от рабочих потоков или новых запросов от клиентов. Всё проходит через общую циклическую очередь Tcl, поэтому передача сообщений между потоками естественным образом работает с вводом-выводом на основе событий, vwait по переменным и так далее. Для передачи больших объёмов информации можно перемещать каналы между потоками.Для расширенных многопоточных скриптов также поддерживается доступ на уровне скрипта к двум основным синхронизационным примитивам: мьютексу и переменным состояния.
См. также
http:// .tcl .tk/doc/howto/thread_model .html, tpool, tsv, ttrace
Licensed under Tcl/Tk terms
https://www.tcl.tk/man/tcl/ThreadCmd/thread.htm