【问题标题】:Run thread with object lifetime运行具有对象生命周期的线程
【发布时间】:2015-07-10 16:47:38
【问题描述】:

在我的班级A 中,只要对象的生命周期,就有一个线程在运行。现在我有一个布尔成员变量,每个循环都会检查它,并且在析构函数中这个变量设置为 false。

class A {
public:
    A() : mRun(true) {
        mThread = std::thread(&A::DoWork(), this);
    }

    ~A() {
        mRun = false;
    }

private:
    bool        mRun;
    std::thread mThread;

    void DoWork() {
        while (mRun) {
            ...
        }
    }
};

是否可以安全地使用while(true)?我读到了线程的销毁,它们将被终止。

【问题讨论】:

  • 请改用std::atomic_bool。它是线程安全的,而您的示例不是。
  • @Phantom:即使使用 atomic bool,他的示例也不是线程安全的,因为它会在线程仍在运行时破坏线程

标签: c++ multithreading


【解决方案1】:

“是否可以安全地使用while(true)?”

是的(假设 while(true) 实际上是指while (mRun))。您需要使 mRun 成员对来自不同线程的并发读/写访问安全。最简单的方法是使用std::atomic<> 值,如下所示:

class A {
public:
    A() : mRun(true) {
        mThread = std::thread(&A::DoWork(), this);
    }

    ~A() {
        mRun = false; // <<<< Signal the thread loop to stop
        mThread.join(); // <<<< Wait for that thread to end
    }

private:
    std::atomic<bool>        mRun; // Use a race condition safe data 
                                   // criterium to end that thread loop
    std::thread mThread;

    void DoWork() {
        while (mRun == true) {
            ...
        }
    }
};

mRun == true 应该回退到 std::atomic::operator T() 函数的位置。

【讨论】:

    【解决方案2】:

    有关如何使 OP 的问题线程安全的直接说明,请阅读第一段。其余部分描述了执行退出条件的另一种方法。

    是的,您可以在代码中安全地使用while(mRun)。为了使您现有的示例线程安全调用mthread.join(),以便线程在线程仍在运行时不会执行其析构函数。


    控制线程执行的另一种方法是使用异常和异常处理程序来逃避线程循环。这是通过在循环外使用 try catch 语句来完成的。这是boost::thread 使用的方法所以在你的方法中你会调用my_thread.interrupt_check() 这会抛出一个异常。这将允许展开堆栈并销毁堆栈上的任何对象。但是你必须小心使用这种方法。如果线程中没有捕获到异常,程序将调用std::terminate 并且您的整个程序将停止。这种方法的一个例子是这样的:

    void interrupt_check();
    
    class interruptible_thread
    {
    private:
        static thread_local std::atomic<bool>* interrupt_flag;
        friend void interrupt_check();
    
        std::atomic<bool> flag;
        std::thread thread_obj;
    
        struct interrupt_exception { };
    
    public:
        template<typename _Fn, typename... _Args>
        interruptible_thread(_Fn& function, _Args& arguments) :
            flag(false),
            thread_obj([](std::atomic<bool>* flag_var, _Fn&& function, _Args&& arguments)
                { interrupt_flag = flag_var; 
                  try
                  {
                      function(std::forward<_Args>(arguments));
                  } catch (interrupt_exception& e) { }
                },
                &interrupt_flag,
                std::forward<_Fn>(function),
                std::forward<_Args>(arguments))
        { }
    
        void interrupt() { flag = true; }
    
        ~interruptible_thread() { flag = false; interrupt(); thread_obj.join(); }
    }
    
    void interrupt_check()
    {
        if(!interruptible_thread::interrupt_flag)
            throw interruptible_thread::interrupt_exception;
    }
    

    这种方法的优点是析构函数可以正确执行,并且可以在调用者希望时终止线程。缺点是如果线程不检查,那么这只是std::thread,开销更大。

    还要注意,如果std::thread 对象在线程仍可连接时被销毁,则调用std::terminate。我想这不是将线程包装在对象中的意图。

    【讨论】:

      【解决方案3】:

      您必须记住,mRun 只会设置为 false,因为对象正在被销毁。由于它在自己的线程上,这不是一个好主意。一个更好的主意是使用设置的全局布尔值,这个类可以使用原子操作,以防止两个线程写入它,(例如,线程 A 将其设置为 FALSE,而线程 B 在它后面,并且将其设置回 TRUE,无论出于何种原因 - 坏主意...),这样,当对象被销毁时,您可以检测到它不再存在,而不是尝试访问它。

      当我做这样的事情时,我倾向于将线程本身的一个实例传递给我的 RUN 函数,所以我可以像访问它一样访问它

      while(threadInstance->mRun)
      

      但这可能不适合你。

      【讨论】:

        猜你喜欢
        • 2011-04-11
        • 2011-02-19
        • 1970-01-01
        • 1970-01-01
        • 2016-10-03
        • 1970-01-01
        • 2015-10-04
        • 2019-02-08
        • 2022-06-12
        相关资源
        最近更新 更多