如何退出后台线程循环?

How to exit from a background thread loop?

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

这是一个简化版本:

    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 stuff\n");
            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("hello\n");

        g_stop = true;
        g_cond.notify_one();
        g_thread.join();
    }

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

之后

对此是否有简单的解决方案,或其他更好的设计模式?

您正在读取和写入 g_stop 变量,但没有任何同步(例如使用原子操作,或使用互斥锁来序列化对其的访问)。那是一场数据竞赛,这是未定义的行为。

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

为了确保循环线程可以看到对变量的写入,您应该使用 std::atomic<bool> 或在所有 reads/writes 到该变量之前锁定互斥锁。如果您使用 atomic<bool> 来修复未定义的行为,但不能确保线程不会等待条件变量,因为正如您所建议的那样,检查 [= 的值之间有一个 window 11=] 并进入睡眠状态,其中主线程可以设置 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 stuff\n");
        g_cond.wait_for(lock, std::chrono::seconds(15));
    }   
}   

int main(int argc, char* argv[])
{           
    g_stop = false;
    g_thread = std::thread(threadLoop);

    printf("hello\n");

    {
      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_one() 调用,但在这两种情况下,它都会立即看到 g_stop == true 并停止循环。