Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: Nicola BonelliОтветов: 8

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

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

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

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

c++multithreadingc++11

Ответы

Ответ 1 · рейтинг 17 · peterchen

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

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

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

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

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


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

Ответ 2 · рейтинг 10 · Vicente Botet Escriba

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

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

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

Ответ 3 · рейтинг 6 · Marcelo Cantos

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

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

Ответ 4 · рейтинг 5 · user328543

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

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

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

Ответ 5 · рейтинг 5 · Michael Aaron Safyan

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

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

Ответ 6 · рейтинг 5 · dimitri

Вот моя скромная реализация обработчика потоков (для 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;
}

Ответ 7 · рейтинг 4 · Fabio Ceconello

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

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

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

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

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

Ответ 8 · рейтинг 2 · Alex F

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

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы