使用 std::atomic 与 std::condition_variable 相比的方法 wrt 在 C++ 中暂停和恢复 std::thread
Approach of using an std::atomic compared to std::condition_variable wrt pausing & resuming an std::thread in C++
这是一个单独的问题,但与我问的上一个问题有关
我在 C++
代码中使用 std::thread
来不断轮询某些数据并将其添加到缓冲区。我使用 C++ lambda
像这样启动线程:
StartMyThread() {
thread_running = true;
the_thread = std::thread { [this] {
while(thread_running) {
GetData();
}
}};
}
thread_running
是在 class header 中声明的 atomic<bool>
。这是我的 GetData
函数:
GetData() {
//Some heavy logic
}
接下来我还有一个 StopMyThread
函数,我将 thread_running
设置为 false 以便它退出 lambda block
.
中的 while 循环
StopMyThread() {
thread_running = false;
the_thread.join();
}
据我了解,我可以使用 std::condition_variable
暂停和恢复线程,正如我之前的问题 所指出的那样。
但是如果我只使用 std::atomic<bool>
thread_running
执行或不执行 GetData()
中的逻辑是否有缺点,如下所示?
GetData() {
if (thread_running == false)
return;
//Some heavy logic
}
与所述使用std::condition_variable
的方法相比,这会消耗更多CPU个周期吗?
除非我遗漏了什么,否则您已经在您的原始问题中回答了这个问题:您将在每次需要时创建和销毁工作线程。这可能是也可能不是您实际应用程序中的问题。
有两个不同的问题正在解决,这可能取决于您实际在做什么。一个问题是 "I want my thread to run until I tell it to stop." 另一个似乎是 "I have a producer/consumer pair and want to be able to notify the consumer when data is ready." 的情况 thread_running
和 join
方法适用于第一个。第二,您可能想要使用互斥锁和条件,因为您所做的不仅仅是使用状态来触发工作。假设你有一个vector<Work>
。你用互斥量保护它,所以条件变成 [&work] (){ return !work.empty(); }
或类似的东西。当 wait returns 时,你持有互斥锁,这样你就可以把事情从工作中拿走并去做。当你完成后,你回去等待,释放互斥锁,这样生产者就可以向队列中添加东西。
您可能希望结合使用这些技巧。有一个 "done processing" 原子,您的所有线程都会定期检查以了解何时退出,以便您可以加入它们。使用条件覆盖线程间数据传递的情况。
当您想要有条件地停止或不停止另一个线程时,条件变量很有用。所以你可能有一个总是-运行 "worker" 的线程,当它注意到它与 运行.
无关时等待
原子解决方案需要您的 UI 交互与工作线程同步,或者非常复杂的逻辑来异步执行。
作为一般规则,您的 UI 响应线程永远不应在工作线程的非就绪状态下阻塞。
struct worker_thread {
worker_thread( std::function<void()> t, bool play = true ):
task(std::move(t)),
execute(play)
{
thread = std::async( std::launch::async, [this]{
work();
});
}
// move is not safe. If you need this movable,
// use unique_ptr<worker_thread>.
worker_thread(worker_thread&& )=delete;
~worker_thread() {
if (!exit) finalize();
wait();
}
void finalize() {
auto l = lock();
exit = true;
cv.notify_one();
}
void pause() {
auto l = lock();
execute = false;
}
void play() {
auto l = lock();
execute = true;
cv.notify_one();
}
void wait() {
Assert(exit);
if (thread)
thread.get();
}
private:
void work() {
while(true) {
bool done = false;
{
auto l = lock();
cv.wait( l, [&]{
return exit || execute;
});
done = exit; // have lock here
}
if (done) break;
task();
}
}
std::unique_lock<std::mutex> lock() {
return std::unique_lock<std::mutex>(m);
}
std::mutex m;
std::condition_variable cv;
bool exit = false;
bool execute = true;
std::function<void()> task;
std::future<void> thread;
};
或类似的东西。
这拥有一个线程。只要处于 play()
模式,线程就会重复运行任务。如果您 pause()
下一次 task()
完成,工作线程将停止。如果您 play()
在 task()
调用完成之前,它不会注意到 pause()
.
唯一的等待是 worker_thread
的销毁,它会自动通知工作线程它应该退出并等待它完成。
您也可以手动 .wait()
或 .finalize()
。 .finalize()
是异步的,但如果您的应用程序正在关闭,您可以提前调用它并给工作线程更多时间来清理,而主线程在其他地方清理东西。
.finalize()
不可逆
代码未经测试。
这是一个单独的问题,但与我问的上一个问题有关
我在 C++
代码中使用 std::thread
来不断轮询某些数据并将其添加到缓冲区。我使用 C++ lambda
像这样启动线程:
StartMyThread() {
thread_running = true;
the_thread = std::thread { [this] {
while(thread_running) {
GetData();
}
}};
}
thread_running
是在 class header 中声明的 atomic<bool>
。这是我的 GetData
函数:
GetData() {
//Some heavy logic
}
接下来我还有一个 StopMyThread
函数,我将 thread_running
设置为 false 以便它退出 lambda block
.
StopMyThread() {
thread_running = false;
the_thread.join();
}
据我了解,我可以使用 std::condition_variable
暂停和恢复线程,正如我之前的问题
但是如果我只使用 std::atomic<bool>
thread_running
执行或不执行 GetData()
中的逻辑是否有缺点,如下所示?
GetData() {
if (thread_running == false)
return;
//Some heavy logic
}
与std::condition_variable
的方法相比,这会消耗更多CPU个周期吗?
除非我遗漏了什么,否则您已经在您的原始问题中回答了这个问题:您将在每次需要时创建和销毁工作线程。这可能是也可能不是您实际应用程序中的问题。
有两个不同的问题正在解决,这可能取决于您实际在做什么。一个问题是 "I want my thread to run until I tell it to stop." 另一个似乎是 "I have a producer/consumer pair and want to be able to notify the consumer when data is ready." 的情况 thread_running
和 join
方法适用于第一个。第二,您可能想要使用互斥锁和条件,因为您所做的不仅仅是使用状态来触发工作。假设你有一个vector<Work>
。你用互斥量保护它,所以条件变成 [&work] (){ return !work.empty(); }
或类似的东西。当 wait returns 时,你持有互斥锁,这样你就可以把事情从工作中拿走并去做。当你完成后,你回去等待,释放互斥锁,这样生产者就可以向队列中添加东西。
您可能希望结合使用这些技巧。有一个 "done processing" 原子,您的所有线程都会定期检查以了解何时退出,以便您可以加入它们。使用条件覆盖线程间数据传递的情况。
当您想要有条件地停止或不停止另一个线程时,条件变量很有用。所以你可能有一个总是-运行 "worker" 的线程,当它注意到它与 运行.
无关时等待原子解决方案需要您的 UI 交互与工作线程同步,或者非常复杂的逻辑来异步执行。
作为一般规则,您的 UI 响应线程永远不应在工作线程的非就绪状态下阻塞。
struct worker_thread {
worker_thread( std::function<void()> t, bool play = true ):
task(std::move(t)),
execute(play)
{
thread = std::async( std::launch::async, [this]{
work();
});
}
// move is not safe. If you need this movable,
// use unique_ptr<worker_thread>.
worker_thread(worker_thread&& )=delete;
~worker_thread() {
if (!exit) finalize();
wait();
}
void finalize() {
auto l = lock();
exit = true;
cv.notify_one();
}
void pause() {
auto l = lock();
execute = false;
}
void play() {
auto l = lock();
execute = true;
cv.notify_one();
}
void wait() {
Assert(exit);
if (thread)
thread.get();
}
private:
void work() {
while(true) {
bool done = false;
{
auto l = lock();
cv.wait( l, [&]{
return exit || execute;
});
done = exit; // have lock here
}
if (done) break;
task();
}
}
std::unique_lock<std::mutex> lock() {
return std::unique_lock<std::mutex>(m);
}
std::mutex m;
std::condition_variable cv;
bool exit = false;
bool execute = true;
std::function<void()> task;
std::future<void> thread;
};
或类似的东西。
这拥有一个线程。只要处于 play()
模式,线程就会重复运行任务。如果您 pause()
下一次 task()
完成,工作线程将停止。如果您 play()
在 task()
调用完成之前,它不会注意到 pause()
.
唯一的等待是 worker_thread
的销毁,它会自动通知工作线程它应该退出并等待它完成。
您也可以手动 .wait()
或 .finalize()
。 .finalize()
是异步的,但如果您的应用程序正在关闭,您可以提前调用它并给工作线程更多时间来清理,而主线程在其他地方清理东西。
.finalize()
不可逆
代码未经测试。