Может ли вызов unpark до park привести к вечному блокированию потока Rust?
Нет. unpark оставляет для потока один разрешающий токен, поэтому последующий park сразу его потребляет и не блокируется. Это предотвращает потерю уведомления, если пробуждение произошло раньше ожидания.
Пара park/unpark предназначена для низкоуровневой координации потоков: один поток может приостановиться, а другой — адресно разрешить ему продолжить работу. Подход решает типичную проблему примитивных сигналов, где уведомление, отправленное до начала ожидания, могло бы исчезнуть.
Механизм намеренно минимален: он не хранит очередь событий и не описывает условие готовности. Поэтому его обычно используют как строительный блок для планировщиков, пулов потоков и других примитивов синхронизации.
Поток-работник проверяет, что работы нет, и собирается уснуть. Почти одновременно другой поток добавляет работу и отправляет уведомление. Если уведомление не сохраняется, работник может уснуть навсегда, хотя задача уже доступна.
У park/unpark уведомление представлено токеном. Если unpark вызван раньше park, токен сохраняется; если поток уже припаркован, он разблокируется. Однако токен бинарный: несколько уведомлений не превращаются в несколько накопленных событий.
У каждого потока есть логический разрешающий токен, обычно находящийся в состоянии «отсутствует». Вызов unpark переводит его в состояние «доступен», а park либо немедленно потребляет токен, либо блокирует поток до его появления.
Из этого следуют важные свойства:
unpark до park безопасен.unpark схлопываются: наличие двух уведомлений не означает наличие двух токенов.park не является проверкой бизнес-условия: пробуждение нужно рассматривать как повод повторно проверить очередь или состояние.Простейший пример:
Здесь unpark может быть вызван до фактического входа работника в park: разрешение всё равно сохранится. Для очереди задач этого недостаточно само по себе — очередь и условие доступности работы должны быть защищены отдельно, например mutex, атомиками или каналом.
По сравнению с условной переменной park/unpark проще адресовать конкретный поток и не требуется отдельный объект condition variable. Компромисс — отсутствие встроенного предиката, счётчика событий и протокола защиты общих данных; эти гарантии должен предоставить окружающий код.
В пуле потоков работник обрабатывает очередь, а после опустошения должен уснуть. Вариант с постоянным опросом очереди прост, но расходует CPU. Вариант с park/unpark экономичен и не теряет одиночное уведомление при корректном протоколе, но требует mutex для очереди и цикла повторной проверки.
Условная переменная лучше выражает ожидание предиката и удобнее для сложных состояний, однако требует согласованного использования mutex и condition variable. Канал дополнительно передаёт сами задачи и автоматически задаёт протокол доставки, но может быть избыточен для собственного планировщика.
Для небольшого пула выбран mutex с очередью и park/unpark: производитель помещает задачу под mutex, затем вызывает unpark, а работник после пробуждения снова проверяет очередь в цикле. Это снижает простой CPU и сохраняет корректность даже при раннем уведомлении; цена решения — необходимость самостоятельно реализовать завершение пула, гонки и обработку ложных пробуждений.
1. Что произойдёт, если вызвать unpark несколько раз до одного park?
Будет сохранён не счётчик уведомлений, а максимум один разрешающий токен. Один последующий park продолжит выполнение, но информация о количестве вызовов unpark не сохранится. Если каждое событие важно, нужен счётчик, канал или другая структура, учитывающая количество событий.
2. Достаточно ли park/unpark без mutex для реализации очереди задач?
Нет. Пробуждение сообщает лишь о возможном изменении состояния, но не защищает саму очередь и не гарантирует, что задача всё ещё доступна к моменту чтения. Очередь должна иметь собственный протокол синхронизации, а работник — проверять условие в цикле: взять задачу, а если её нет, припарковаться.
3. Является ли unpark отменой сна или способом завершить поток?
Нет. Он лишь делает один токен доступным для конкретного потока и может разблокировать его park. Поток после пробуждения сам решает, продолжать ли работу или завершиться; для остановки нужен отдельный флаг, канал завершения либо другой протокол shutdown.