void interruptible_wait(std::condition_variable& cv,
std::unique_lock
interruption_point();←
(1)
this_thread_interrupt_flag.set_condition_variable(cv);
cv.wait(lk); ←
(2)
this_thread_interrupt_flag.clear_condition_variable();←
(3)
interruption_point();
}
В предположении, что существуют функции, которые устанавливают и разрывают ассоциацию условной переменной с флагом прерывания, этот код выглядит просто и понятно. Он проверяет, не было ли прерывания, ассоциирует условную переменную с флагом interrupt_flag
для текущего потока (1), ждет условную переменную (2), разрывает ассоциацию с условной переменной (3) и снова проверяет, не было ли прерывания. Если поток прерывается во время ожидания условной переменной, то прерывающий поток пошлёт этой переменной сигнал, что пробудит нас и даст возможность проверить факт. К сожалению, этот код не работает, в нем есть две проблемы. Первая довольно очевидна: функция std::condition_variable::wait()
может возбуждать исключения, поэтому из interruptible_wait()
возможен выход без разрыва ассоциации флага прерывания с условной переменной. Это легко исправляется с помощью структуры, которая разрывает ассоциацию в ее деструкторе.
Вторая, не столь очевидная, проблема связана с гонкой. Если поток прерывается после первого обращения к interruption_point()
, но до обращения к wait()
, то не имеет значения, ассоциирована условная переменная с флагом прерывания или нет, потому что wait()
. Если не залезать в код класса std::condition_variable
, то сделать это можно только одним способом: использовать для защиты мьютекс, хранящийся в lk
, который, следовательно, нужно передавать функции set_condition_variable()
. К сожалению, при этом возникают новые проблемы: мы передаём ссылку на мьютекс, о времени жизни которого ничего не знаем, другому потоку (тому, который выполняет прерывание), чтобы тот его захватил (внутри interrupt()
). Но может случиться, что этот поток уже удерживает данный мьютекс, и тогда возникнет взаимоблокировка. К тому же, появляется возможность доступа к уже уничтоженному мьютексу. В общем, это решение не годится. Но если мы не можем interruptible_wait()
. Так какие еще есть варианты? Можно, к примеру, задать таймаут ожидания; использовать вместо wait()
функцию wait_for()
с очень коротким таймаутом (скажем, 1 мс). Это ограничивает сверху время до момента, когда поток обнаружит прерывание (с учетом промежутка между тактами часов). Если поступить так, что ожидающий поток будет видеть больше ложных пробуждений из-за срабатывания таймера, но тут уж ничего не попишешь. Такая реализация показана в листинге ниже вместе с соответствующей реализацией interrupt_flag
.
Листинг 9.11. Реализация interruptible_wait()
для std::condition_variable
с таймаутом
class interrupt_flag {
std::atomic
std::condition_variable* thread_cond;
std::mutex set_clear_mutex;
public:
interrupt_flag(): thread_cond(0) {}
void set() {
flag.store(true, std::memory_order_relaxed);
std::lock_guard
if (thread_cond) {
thread_cond->notify_all();
}
}
bool is_set() const {
return flag.load(std::memory_order_relaxed);
}
void set_condition_variable(std::condition_variable& cv) {
std::lock_guard
thread_cond = &cv
}
void clear_condition_variable() {
std::lock_guard
thread_cond = 0;
}
struct clear_cv_on_destruct {
~clear_cv_on_destruct() {