C ++在析构函数中杀死一个线程

C++ Kill a Thread In Destructor

我有一个 class 启动另一个线程,该线程以固定的时间间隔访问它的一些数据。这意味着我有两个线程访问相同的数据(原始线程和新创建的线程)。这引入了对互斥量的需要。一切顺利,直到 class 的析构函数被调用(在程序末尾)并且内存位置不再有效。此时新线程尝试访问数据并得到一个访问冲突错误(很明显)。

我想做的是在析构函数中停止线程,或者让线程在“注意到”class 实例已被销毁后停止。

这是简化的线程代码(为简洁起见使用了 typedef):

void myClass::StartThread() {
    auto threadFunc = [&, this]() {
        while (true) {
            time_point now = steady_clock::now();
            if (chro::duration_cast<chro::milliseconds>(now - this->m_lastSeedTime).count() > INTERVAL) {
                std::lock_guard<std::mutex> lockGuard(this->m_mut);
                this->m_lastSeedTime = now;
                this->accessData();
            }
        }
    };
    std::thread thread(threadFunc);
    thread.detach();

当然,如果我只是以某种明显的方式处理不当,也请告诉我。

如果你想让一个线程死掉,你应该让它退出。这是干净利落的唯一可靠方法。

改变一下

while (true)

while(this->keepRunning)

并适当同步。要么不分离线程(以便析构函数可以加入它),要么为线程添加某种方式来指示它已经退出(以便析构函数可以等待它)。

哦,线程应该休眠而不是自旋。在那种情况下,如果您不希望析构函数也被阻塞,则需要一些方法来中断睡眠:在睡眠的条件变量上使用定时等待可以使这变得容易。

终止线程不起作用。问题是,如果您确实终止了一个线程,它可能正处于应该作为原子操作执行的多步操作的中间,从而使您的程序处于无效状态。相反,向另一个线程发出自杀信号,并等待它结束。

@Useless 的回答是正确的。以下是具体操作方法:

class myClass{
    ...
private:
    std::thread m_thread;
    std::atomic_bool m_keepRunning{true};
    ....
};


void myClass::StartThread() {
    auto threadFunc = [&, this]() {
        while (m_keepRunning) {
            time_point now = steady_clock::now();
            if (chro::duration_cast<chro::milliseconds>(now - this->m_lastSeedTime).count() > INTERVAL) {
                std::lock_guard<std::mutex> lockGuard(this->m_mut);
                if(!m_keepRunning) break; // destructor called, don't access data
                this->m_lastSeedTime = now;
                this->accessData();
            }
        }
    };
    m_thread = std::thread(threadFunc);
}

myClass::~myClass()
{
    m_keepRunning = false;
    m_mutex.unlock(); // make sure we don't wait in the loop for the lock
    if(m_thread.joinable()) m_thread.join();
    
    // do other cleaning
}

还有一点就是,当你一直在等待INTERVAL的时候,会造成时间上的累积延迟。假设您的间隔是 50 毫秒。当您的 CPU 有太多工作要做或 accessData 函数花费太多时间时,您将无法在 50 毫秒内 运行 下一次迭代。假设它将是 52 毫秒,即 2 毫秒的延迟。这些延迟会及时累积并影响您的精度。

相反,您可以这样做:

time_point waitUntil = steady_clock::now() + initialWaitTime;
while(m_keepRunning){
    if(steady_clock::now() >= waitUntil)
    {
        // ... do your work
        waitUntil = waitUntil + chro::milliseconds(INTERVAL)
    }
}

对于定时等待部分,@Useless 也是正确的。旋转会对你的核心造成沉重的负担。相反,您应该使用条件或 timed_mutex。但是上面的建议仍然有效。不要使用 sleep_for,而是使用 sleep_until