Прерывание потока C++0x

В соответствии с окончательным проектом C++0x невозможно запросить завершение потока. Тем не менее, при необходимости нам нужно реализовать решение «сделай сам».

С другой стороны, boost::thread предоставляет механизм безопасного прерывания потока.

На ваш взгляд, какое лучшее решение? Разрабатываете свой собственный совместный «механизм прерывания» или переходите на родной?


person Nicola Bonelli    schedule 07.05.2010    source источник
comment
Стоит отметить, что если вы можете просто убить поток, это может оставить выделенные ресурсы в памяти (поскольку поток был убит до того, как они были удалены). Поэтому я думаю, что это, по крайней мере, для большинства случаев, хорошее решение.   -  person luiscubal    schedule 07.05.2010
comment
Есть вещи и похуже утечек — например, незавершенные транзакции, поврежденная куча или все еще удерживаемые блокировки.   -  person peterchen    schedule 09.05.2010
comment
если вы заботитесь о правильности, у вас действительно нет выбора: тогда потоки могут только прерываться совместно, когда они находятся в предопределенных прерываемых состояниях.   -  person jalf    schedule 09.05.2010
comment
Убить нить — значит накинуть петлю на шеи всех остальных тредов. Просто не делайте этого, если только вы не хладнокровный убийца тредов...   -  person Jörgen Sigvardsson    schedule 18.08.2011
comment
возможный дубликат Как завершить поток в C++11? Выбрать другой, потому что он немного более общий.   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 15.06.2015


Ответы (8)


Во всех спецификациях языка сказано, что поддержка не встроена в язык. boost::thread::interrupt также нуждается в некоторой поддержке со стороны функции потока:

Когда прерванный поток в следующий раз выполняет одну из указанных точек прерывания (или если он в данный момент заблокирован во время выполнения одной из них)

то есть, когда функция потока не дает вызывающей стороне возможность прервать ее, вы все равно застряли.

Я не уверен, что вы имеете в виду под «переходом на родной язык» - встроенной поддержки нет, если только вы не очарованы boost:threads.

Тем не менее, я бы использовал явный механизм. В любом случае вам нужно подумать о достаточном количестве точек прерывания, почему бы не сделать их явными? По моему опыту, дополнительный код обычно незначителен, хотя вам может потребоваться изменить некоторые ожидания с одного объекта на несколько объектов, что, в зависимости от вашей библиотеки, может выглядеть более уродливым.


Можно также вытащить «не использовать исключения для потока управления», но по сравнению с возиться с потоками, это просто рекомендация.

person peterchen    schedule 09.05.2010
comment
Другой вариант, также упомянутый в www2.research.att .com/~bs/C++0xFAQ.html#std-thread — это going native, использующий thread::native_handle() для получения доступа к дескриптору потока операционной системы, чтобы реализовать прерывание потока. - person Nicola Bonelli; 09.05.2010
comment
Спасибо, Никола. Нативная IMO против библиотеки зависит от других вещей - как правило, я бы не полагался на interrupt. - person peterchen; 09.05.2010

Использование собственного дескриптора для отмены потока — плохой вариант в C++, так как вам нужно уничтожить все объекты, выделенные в стеке. Это была основная причина, по которой они не включили операцию отмены.

Boost.Thread предоставляет механизм прерывания, который должен объединяться в любой ожидающий примитив. Поскольку в качестве общего механизма это может быть дорого, стандарт не включает его.

Вам нужно будет реализовать его самостоятельно. См. мой ответ здесь на аналогичный вопрос о том, как реализовать это самостоятельно. Чтобы завершить решение, прерывание должно быть брошено, когда прерывание истинно, и поток должен поймать это прерывание и завершиться.

person Vicente Botet Escriba    schedule 16.05.2010

Упреждающее завершение потока небезопасно, потому что после этого состояние всего процесса становится неопределенным. Поток мог получить критическую секцию перед завершением. Этот критический раздел теперь никогда не будет выпущен. Куча может стать навсегда заблокированной и так далее.

Решение boost::thread::interrupt работает, если попросить красиво. Он будет прерывать только поток, выполняющий что-то, что можно прервать, например ожидание условной переменной Boost.Thread, или если поток выполняет одно из этих действий после вызова прерывания. Но даже в этом случае поток не проходит бесцеремонно через мясорубку, как, скажем, функция TerminateThread в Win32, а просто вызывает исключение, которое, если вы хорошо себя ведете и везде используете RAII, уберет после себя и изящно выйти из потока.

person Marcelo Cantos    schedule 15.05.2010

Завершать поток небезопасно, так как вы не сможете контролировать состояние каких-либо структур данных, над которыми он работает в этот момент.

Если вы хотите прервать работающий поток, вы должны реализовать свой собственный механизм. ИМХО, если вам это нужно, ваш дизайн не подготовлен для нескольких потоков.

Если вы просто хотите дождаться завершения потока, используйте функцию join() или future.

person user328543    schedule 07.05.2010
comment
Есть много причин для прерывания работающего потока, и это вовсе не говорит о плохом дизайне. - person Chris O; 16.05.2010
comment
-1, вы полностью игнорируете способ прерывания в ускорении. Это детерминированный способ, включающий создание исключения из некоторых предопределенных мест. Целостность данных, над которыми работал поток, гарантируется безопасностью исключений. - person Yakov Galka; 22.06.2011
comment
Например: в эвристическом алгоритме может быть несколько потоков, ищущих решение в разных пространствах. Как только решение найдено одним потоком, другие потоки должны быть уничтожены или отменены. Хорошо, я согласен, что мы тоже можем закодировать его вручную, но это выглядит как базовое требование. - person balki; 11.02.2012
comment
Чтобы немного расширить ответ ybungalobill: вызов boost::thread terminate() НЕ (обязательно) использует грубый механизм завершения потока операционной системы. Вместо этого он генерирует специальное исключение в определенных точках прерывания, которые находятся под контролем разработчика приложения. - person Nanno Langstraat; 12.12.2013
comment
Не обращая внимания на многие детали, точка прерывания потока вроде 'boost::this_thread::sleep()' /может/ выглядеть внутри так: pthread_cond_wait(&hidden_cond, &hidden_mutex); если (hidden_terminate_flag) бросить boost::thread_terminated(); (Использование переменной условия pthread позволит завершить спящий поток без опроса.) Имейте в виду, что это чистая спекуляция; Фактическая реализация boost::thread может быть совершенно другой. Это просто для того, чтобы дать представление о том, как правила boost::thread предлагают достаточно хорошее стандартное прерывание потока, которое не выходит за рамки обычных механизмов C++. - person Nanno Langstraat; 12.12.2013

Внедрение решения «сделай сам» имеет наибольший смысл, и на самом деле это не должно быть так сложно сделать. Вам понадобится общая переменная, которую вы читаете/записываете синхронно, указывая, запрашивается ли завершение потока, и ваш поток периодически читает из этой переменной, когда он находится в состоянии, когда его можно безопасно прервать. Когда вы хотите прервать поток, вы просто синхронно пишете в эту переменную, а затем присоединяетесь к потоку. Предполагая, что он взаимодействует должным образом, он должен заметить, что переменная была записана и закрыта, в результате чего функция соединения больше не блокируется.

Если бы вы стали туземцами, вы бы ничего от этого не выиграли; вы бы просто выбросили все преимущества стандартного и кросс-платформенного механизма многопоточности ООП. Чтобы ваш код был правильным, поток должен быть закрыт совместно, что подразумевает взаимодействие, описанное выше.

person Michael Aaron Safyan    schedule 13.05.2010

Вот моя скромная реализация обработчика потоков (для C++0x). Я надеюсь, что это будет полезно.

// Class cancellation_point
#include <mutex>
#include <condition_variable>

struct cancelled_error {};

class cancellation_point
{
public:
    cancellation_point(): stop_(false) {}

    void cancel() {
        std::unique_lock<std::mutex> lock(mutex_);
        stop_ = true;
        cond_.notify_all();
    }

    template <typename P>
    void wait(const P& period) {
        std::unique_lock<std::mutex> lock(mutex_);
        if (stop_ || cond_.wait_for(lock, period) == std::cv_status::no_timeout) {
            stop_ = false;
            throw cancelled_error();
        }
    }
private:
    bool stop_;
    std::mutex mutex_;
    std::condition_variable cond_;
};


// Usage example
#include <thread>
#include <iostream>

class ThreadExample
{
public:
    void start() {
        thread_ = std::unique_ptr<std::thread>(
            new std::thread(std::bind(&ThreadExample::run, this)));
    }
    void stop() {
        cpoint_.cancel();
        thread_->join();
    }
private:
    void run() {
        std::cout << "thread started\n";
        try {
            while (true) {
                cpoint_.wait(std::chrono::seconds(1));
            }
        } catch (const cancelled_error&) {
            std::cout << "thread cancelled\n";
        }
    }
    std::unique_ptr<std::thread> thread_;
    cancellation_point cpoint_;
};

int main() {
    ThreadExample ex;
    ex.start();
    ex.stop();
    return 0;
}
person dimitri    schedule 18.08.2011

В моей реализации потоков используется идиома pimpl, и в классе Impl у меня есть одна версия для каждой ОС, которую я поддерживаю, а также одна, которая использует ускорение, поэтому я могу решить, какую из них использовать при создании проекта.

Я решил создать два класса: один — Thread, который имеет только базовые сервисы, предоставляемые ОС; а другой — SafeThread, который наследуется от Thread и имеет метод для совместного прерывания.

У потока есть метод terminate(), который выполняет навязчивое завершение. Это виртуальный метод, который перегружен в SafeThread, где он сигнализирует об объекте события. Существует (статический) метод yeld(), который время от времени должен вызывать работающий поток; этот метод проверяет, сигнализируется ли объект события, и, если да, генерирует исключение, перехваченное в вызывающей стороне точки входа потока, тем самым завершая поток. Когда это происходит, он сигнализирует о втором объекте события, поэтому вызывающая функция terminate() может знать, что поток был безопасно остановлен.

В случаях, когда существует риск взаимоблокировки, SafeThread::terminate() может принимать параметр тайм-аута. Если тайм-аут истекает, он вызывает Thread::terminate(), тем самым навязчиво убивая поток. Это последний ресурс, когда у вас есть что-то, что вы не можете контролировать (например, сторонний API), или в ситуациях, когда взаимоблокировка наносит больший ущерб, чем утечка ресурсов и тому подобное.

Надеюсь, это будет полезно для вашего решения и даст вам достаточно четкое представление о моем выборе дизайна. Если нет, я могу опубликовать фрагменты кода, чтобы уточнить, если хотите.

person Fabio Ceconello    schedule 16.05.2010
comment
Без обид, но какое отношение ваша конкретная реализация потока имеет к std::thread и boost::thread? - person Billy ONeal; 17.05.2010
comment
Извините, если я был недостаточно ясен, рекомендация, которую я хотел дать, заключалась в том, чтобы не использовать ни один из них напрямую, вместо этого иметь свои собственные классы-оболочки, которые могли бы использовать их внутри или нет, и по вашему выбору дополнять их низкоуровневым кодом. не делая ваше приложение зависимым от него. Учитывая текущее состояние поддержки многопоточности в C++, я считаю такой подход необходимым и решающим поставленную задачу. Суть в том, что мой ответ на вопрос будет таким: пишите свои собственные классы, но не обязательно код для их поддержки: используйте boost, std или свой собственный код, где это удобно. - person Fabio Ceconello; 17.05.2010

Я согласен с этим решением. Например, .NET позволяет прерывать любой рабочий поток, а я никогда не использую эту возможность и не рекомендую делать это ни одному профессиональному программисту. Я хочу сам решать, когда рабочий поток может быть прерван и как это сделать. Это отличается для оборудования, ввода-вывода, пользовательского интерфейса и других потоков. Если поток может быть остановлен в любом месте, это может вызвать неопределенное поведение программы с управлением ресурсами, транзакциями и т. д.

person Alex F    schedule 07.05.2010