如何退出后台线程循环?

ler*_*mjs 1 c++ multithreading stl condition-variable stdthread

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

这是一个简化版本:

    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();
    }
Run Code Online (Sandbox Code Playgroud)

当我运行这个测试程序时,我希望它能够快速退出,但有时它会卡在wait_for()中.我想也许在线程在wait_for()中休眠之前发生了notify_one(),但是在检查了g_stop之后.

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

Jon*_*ely 5

您正在读取和编写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 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();
}
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为循环线程在检查g_stop时保持对互斥锁的锁定,并且它一直保持该锁定,直到它开始在condvar上等待.主线程将锁定设置g_stop = true,它只能在另一个线程等待时执行.

这意味着现在只有两种可能的执行.g_stop = true当线程在condvar上等待时发生,并且它在notify_one()调用之前唤醒,或者因为 notify_one()调用而唤醒,但在这两种情况下它都会g_stop == true立即看到并停止循环.