bool is_set() const;
};
thread_local interrupt_flag this_thread_interrupt_flag; ←
(1)
class interruptible_thread {
std::thread internal_thread;
interrupt_flag* flag;
public:
template
interruptible_thread(FunctionType f) {
std::promise
(2)
internal_thread = std::thread([f,&p] { ←
(3)
p.set_value(&this_thread_interrupt_flag);
f(); ←
(4)
});
flag = p.get_future().get(); ←
(5)
}
void interrupt() {
if (flag) {
flag->set(); ←
(6)
}
}
};
Переданная функция f
обертывается лямбда-функцией (3), которая хранит копию f
и ссылку на локальный объект-обещание p
(2). Перед тем как вызывать переданную функцию (4), лямбда-функция устанавливает в качестве значения обещания адрес переменной this_thread_interrupt_flag
(объявленной с модификатором thread_local
(1)) в новом потоке. Затем вызывающий поток дожидается готовности будущего результата, ассоциированного с обещанием, и сохраняет этот результат в переменной-члене flag
(5). Отметим, что лямбда-функция исполняется в новом потоке и хранит висячую ссылку на локальную переменную p
, но ничего страшного в этом нет, так как конструктор interruptible_thread
ждет, пока на p
не останется ссылок в новом потоке, и только потом возвращает управление. Еще отметим, что эта реализация не обрабатывает присоединение или отсоединение потока. Мы сами должны позаботиться об очистке переменной flag
в случае выхода или отсоединения потока, чтобы избежать появления висячего указателя.
Теперь написать функцию interrupt()
несложно: имея указатель на флаг прерывания, мы знаем, какой поток прерывать, поэтому достаточно просто поднять этот флаг (6). Что делать дальше, решает сам прерываемый поток. О том, как принимается это решение, мы и поговорим ниже.
9.2.2. Обнаружение факта прерывания потока
Итак, мы умеем устанавливать флаг прерывания, но толку от него чуть, если поток не проверяет, что его хотят прервать. В простейшем случае это можно сделать с помощью функции interruption_point()
, которую можно вызывать в точке, где прерывание безопасно. Если флаг прерывания установлен, то эта функция возбуждает исключение thread_interrupted
:
void interruption_point() {
if (this_thread_interrupt_flag.is_set()) {
throw thread_interrupted();
}
}
Обращаться к этой функции можно там, где нам удобно:
void fоо() {
while (!done) {
interruption_point();
process_next_item();
}
}
Такое решение работает, но оно не идеально. Лучше всего прерывать поток тогда, когда он блокирован в ожидании чего-либо, но именно в этот момент поток как раз и не работает, а, значит, не может вызвать interruption_point()
! Нам требуется какой-то механизм прерываемого ожидания.
9.2.3. Прерывание ожидания условной переменной
Итак, мы можем обнаруживать прерывание в подходящих местах программы с помощью обращений к функции interruption_point()
, но это ничем не помогает в случае, когда поток блокирован в ожидании какого-то события, например сигнала условной переменной. Нам необходима еще одна функция, interruptible_wait()
, которую можно будет перегрузить для различных ожидаемых событий, и нужно придумать, как вообще прерывать ожидание. Я уже говорил, что среди прочего ожидать можно сигнала условной переменной, поэтому с нее и начнем. Что необходимо для того, чтобы можно было прервать поток, ожидающий условную переменную? Проще всего было бы известить условную переменную в момент установки флага и поставить точку прерывания сразу после ожидания. Но в этом случае придётся разбудить все потоки, ждущие эту условную переменную, хотя заинтересован в этом только прерываемый поток. Впрочем, потоки, ждущие условную переменную, в любом случае должны обрабатывать ложные пробуждения, а отличить посланный нами сигнал от любого другого они не могут, так что ничего страшного не случится. В структуре interrupt_flag
нужно будет сохранить указатель на условную переменную, чтобы при вызове set()
ей можно было послать сигнал. В следующем листинге показана возможная реализация функции interruptible_wait()
для условных переменных.
Листинг 9.10. Неправильная реализация interruptible_wait()
для std::condition_variable