[英]Is using std::future the best way to safely stop a thread?
我读这对如何安全地从线程退出教程。
本质上,它将将来对象传递给将要从线程执行的函数,并在每个while循环中检查该将来是否已具有值(如果已存在,则退出线程)。 看到:
void threadFunction(std::future<void> futureObj)
{
std::cout << "Thread Start" << std::endl;
while (futureObj.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout)
{
std::cout << "Doing Some Work" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
std::cout << "Thread End" << std::endl;
}
问题是,为了检查是否已设置futureObj
,它必须等待一段时间(此处为1毫秒)。 因此,我在线程的每次迭代上都损失了1毫秒。 这不应该是更可取的:
void threadFunction(bool *shouldStop)
{
std::cout << "Thread Start" << std::endl;
while (!*shouldStop)
{
std::cout << "Doing Some Work" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
std::cout << "Thread End" << std::endl;
}
为了检查shouldStop
,不浪费时间。 所以我的线程运行得更快。 他为什么不这样做?
更新:我猜这是一个应该做同样事情的简单类。
class Stoppable
{
private:
std::atomic<bool> _shouldContinue;
public:
Stoppable()
{
_shouldContinue.store(true);
}
virtual void run() = 0;
void operator()()
{
run();
}
bool shouldContinue()
{
return _shouldContinue.load();
}
void stop()
{
_shouldContinue.store(false);
}
};
然后,要使用它,只需将Stoppable
子类化并执行以下操作:
void MySubClass::run()
{
while (shouldContinue())
{
//...
}
}
第二个建议:
void threadFunction(bool *shouldStop)
如果您打算从另一个线程设置*shouldStop
,恐怕是错误的。
在这种情况下,它应该为void threadFunction(std::atomic<bool> *shouldStop)
。
没有一种“正确的方式”向线程发出停止信号,因为这取决于您是希望线程继续工作还是仅在有工作要做时工作。 但是有错误的方法-通常涉及不确定的行为,因为在一个线程中写入非原子变量,而在另一个线程中读取非原子变量。
如果您希望线程一直运行直到停止(例如,这在图形渲染线程或实时游戏逻辑线程中很常见),那么就没有理由等待检查停止信号。
如果您的线程是仅在有工作要做时才运行的工作线程,则使用std::condition_variable
, std::mutex
和队列以及信号更为正常。
如果很有趣,这是一个工作队列实现的示例。 请注意,这允许在提交方面乱序完成工作。 有许多可用的策略:
#include <condition_variable>
#include <mutex>
#include <queue>
#include <functional>
#include <thread>
#include <vector>
#include <iostream>
#include <iomanip>
struct worker_control
{
std::queue<std::function<void()>> work_queue;
std::mutex m;
std::condition_variable cv;
bool stop_signal = false;
void post_work(std::function<void()> f)
{
auto lock = std::unique_lock(m);
work_queue.push(std::move(f));
lock.unlock();
cv.notify_one();
}
void request_stop()
{
auto lock = std::unique_lock(m);
stop_signal = true;
lock.unlock();
cv.notify_all();
}
};
std::mutex emit_mutex;
template<class...Args>
void emit(Args&&...args)
{
auto lock = std::unique_lock(emit_mutex);
std::cout << std::this_thread::get_id() << " : ";
((std::cout << args), ...);
std::cout << '\n';
}
void run_worker(worker_control& control)
{
auto should_run = [&]
{
return not control.work_queue.empty() or control.stop_signal;
};
while (1)
{
auto lock = std::unique_lock(control.m);
control.cv.wait(lock, should_run);
// at this point we own the lock on control.m
if (not control.work_queue.empty())
{
auto work = std::move(control.work_queue.front());
control.work_queue.pop();
lock.unlock(); // allow other workers access to the queue
work();
}
else
{
// we can only have got here if there is no work to do and we have been asked to stop
return;
}
}
}
int main()
{
std::vector<std::thread> worker_threads;
auto control = worker_control();
worker_threads.emplace_back([&]{ run_worker(control); });
worker_threads.emplace_back([&]{ run_worker(control); });
worker_threads.emplace_back([&]{ run_worker(control); });
control.post_work([]{ emit("the"); });
control.post_work([]{ emit("quick"); });
control.post_work([]{ emit("brown"); });
control.post_work([]{ emit("fox"); });
control.post_work([]{ emit("jumps"); });
control.post_work([]{ emit("over"); });
control.post_work([]{ emit("the"); });
control.post_work([]{ emit("lazy"); });
control.post_work([]{ emit("dog"); });
control.request_stop();
for (auto& t : worker_threads)
if (t.joinable())
t.join();
}
输出示例:
140244960823040 : the
140244960823040 : fox
140244960823040 : jumps
140244960823040 : over
140244960823040 : the
140244960823040 : lazy
140244960823040 : dog
140244969215744 : quick
140244952430336 : brown
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.