如何退出后台线程循环

How to exit from a background thread loop?

本文关键字:后台 线程 循环 退出 何退出      更新时间:2023-10-16

我有一个用于上传文件的后台线程。它是循环运行的;它做一些工作,然后休眠,直到超时或通过条件变量明确通知它还有更多的工作要做。问题是有时我无法让线程快速退出。

这是一个简化版本:

    std::thread g_thread;
    std::mutex g_mutex;
    std::condition_variable g_cond;
    bool g_stop = false;
    void threadLoop()
    {   
        while (!g_stop)
        {   
            printf("doing some stuffn");
            std::unique_lock<std::mutex> lock(g_mutex);
            g_cond.wait_for(lock, std::chrono::seconds(15));
        }   
    }   
    int main(int argc, char* argv[])
    {           
        g_stop = false;
        g_thread = std::thread(threadLoop);
        printf("hellon");
        g_stop = true;
        g_cond.notify_one();
        g_thread.join();
    }

当我运行这个测试程序时,我希望它能很快退出,但有时它会卡在wait_forr((中。我认为notify_one((可能发生在线程在wait_fr((中休眠之前,但在检查g_stop之后。

有没有一个简单的解决方案,或者另一种更有效的设计模式?

在没有任何同步的情况下读取和写入g_stop变量(例如使用原子操作或使用互斥体序列化对它的访问(。这是一场数据竞赛,是一种未定义的行为。

因为不能安全地访问它,所以编译器可以假设没有其他线程修改过g_stop,所以在threadLoop函数中,它可以将它加载到寄存器中一次,然后再也不读取变量,而只是保持循环。

为了确保循环线程能够看到对变量的写入,您应该使用std::atomic<bool>或在对该变量的所有读取/写入之前锁定互斥对象。如果您使用atomic<bool>来修复未定义的行为,但不能确保线程不会等待条件变量,因为正如您所建议的,在检查g_stop的值和进入睡眠之间有一个窗口,在这个窗口中,主线程可以设置g_stop = true并向condvar发信号,因此循环线程不会等到notify_one((调用之后,因此会错过它。

这个稍微改变的版本将确保线程不会等待条件变量,如果主线程已经告诉它停止:

std::thread g_thread;
std::mutex g_mutex;
std::condition_variable g_cond;
bool g_stop = false;
void threadLoop()
{   
    std::unique_lock<std::mutex> lock(g_mutex);
    while (!g_stop)
    {   
        printf("doing some stuffn");
        g_cond.wait_for(lock, std::chrono::seconds(15));
    }   
}   
int main(int argc, char* argv[])
{           
    g_stop = false;
    g_thread = std::thread(threadLoop);
    printf("hellon");
    {
      std::lock_guard<std::mutex> lock(g_mutex);
      g_stop = true;
    }
    g_cond.notify_one();
    g_thread.join();
}

这是因为循环线程在检查g_stop时对互斥锁持有锁,并且一直持有该锁,直到开始等待condvar。主线程获取锁来设置g_stop = true,这只能在其他线程等待时进行。

这意味着现在只有两次可能的处决。g_stop = true发生在线程等待condvar时,它要么在notify_one((调用之前唤醒,要么因为notify_none((调用的而唤醒,但在这两种情况下,它都会立即看到g_stop == true并停止循环。