编写一个保持活动状态的线程
Writing a thread that stays alive
我想写一个 class 环绕 std::thread 并且表现得像 std::thread 但是每次我需要处理一些异步的时候都不会实际分配一个线程。原因是我需要在不允许动态分配的上下文中使用多线程,而且我也不希望有创建 std::thread 的开销。
相反,我希望线程在循环中 运行 并等待它可以开始处理。客户端调用 invoke
唤醒线程。 Thread 锁定一个互斥量,它正在处理并再次入睡。函数 join
的行为类似于 std::thread::join,它通过锁定直到线程释放锁(即再次入睡)。
我想我得到了 class 到 运行 但是由于在多线程方面普遍缺乏经验,我想问一下是否有人可以发现竞争条件或者我使用的方法被认为是 "good style"。例如,我不确定临时锁定互斥量是否是 "join" 线程的一种好方法。
编辑
我发现了另一个竞争条件:当在 invoke
之后直接调用 join
时,线程没有理由已经锁定了互斥量并因此锁定了 join
的调用者,直到线程进入休眠状态。为防止这种情况,我必须为调用计数器添加检查。
页眉
#pragma once
#include <thread>
#include <atomic>
#include <mutex>
class PersistentThread
{
public:
PersistentThread();
~PersistentThread();
// set function to invoke
// locks if thread is currently processing _func
void set(const std::function<void()> &f);
// wakes the thread up to process _func and fall asleep again
// locks if thread is currently processing _func
void invoke();
// mimics std::thread::join
// locks until the thread is finished with it's loop
void join();
private:
// intern thread loop
void loop(bool *initialized);
private:
bool _shutdownRequested{ false };
std::mutex _mutex;
std::unique_ptr<std::thread> _thread;
std::condition_variable _cond;
std::function<void()> _func{ nullptr };
};
源文件
#include "PersistentThread.h"
PersistentThread::PersistentThread()
{
auto lock = std::unique_lock<std::mutex>(_mutex);
bool initialized = false;
_thread = std::make_unique<std::thread>(&PersistentThread::loop, this, &initialized);
// wait until _thread notifies, check bool initialized to prevent spurious wakeups
_cond.wait(lock, [&] {return initialized; });
}
PersistentThread::~PersistentThread()
{
{
std::lock_guard<std::mutex> lock(_mutex);
_func = nullptr;
_shutdownRequested = true;
// wake up and let join
_cond.notify_one();
}
// join thread,
if (_thread->joinable())
{
_thread->join();
}
}
void PersistentThread::set(const std::function<void()>& f)
{
std::lock_guard<std::mutex> lock(_mutex);
this->_func = f;
}
void PersistentThread::invoke()
{
std::lock_guard<std::mutex> lock(_mutex);
_cond.notify_one();
}
void PersistentThread::join()
{
bool joined = false;
while (!joined)
{
std::lock_guard<std::mutex> lock(_mutex);
joined = (_invokeCounter == 0);
}
}
void PersistentThread::loop(bool *initialized)
{
std::unique_lock<std::mutex> lock(_mutex);
*initialized = true;
_cond.notify_one();
while (true)
{
// wait until we get the mutex again
_cond.wait(lock, [this] {return _shutdownRequested || (this->_invokeCounter > 0); });
// shut down if requested
if (_shutdownRequested) return;
// process
if (_func) _func();
_invokeCounter--;
}
}
我不明白你到底想问什么。你用的这个款式不错
使用 bools
并检查单个 routines
会更安全,因为 void
returns 什么都没有,所以你可能会被错误卡住。检查所有你能做的事情,因为线程在后台运行。如果该过程真的成功,请确保调用 运行 正确。你也可以阅读一些关于 "Thread Pooling".
的东西
你问的是潜在的竞争条件,我在显示的代码中看到了至少一个竞争条件。
构造了一个PersistentThread
后,不保证新线程会在主执行线程returns之前从构造函数中获取其loop()
中的初始锁,进入invoke()
。有可能主执行线程在构造函数完成后立即进入invoke()
,结果没有通知任何人,因为内部执行线程还没有锁定互斥量。因此,此 invoke()
不会导致任何处理发生。
您需要将构造函数的完成与执行线程的初始锁获取同步。
编辑:您的修改看起来正确;但我还发现了另一个竞争条件。
与documented in the description of wait()一样,wait()
可能会“虚假”醒来。仅仅因为 wait()
返回,并不意味着其他线程已进入 invoke()
.
您需要一个计数器,除此之外,invoke()
递增计数器,执行线程仅在计数器大于零时执行其分配的职责,递减它。这将防止虚假 wake-ups.
我也会让执行线程在输入wait()
之前检查计数器,只有当它为0时才输入wait()
。否则,它递减计数器,执行其功能,然后循环返回。
这应该会堵塞该区域中所有潜在的竞争条件。
P.S。虚假 wake-up 也适用于初始通知,在您的更正中,执行线程已进入循环。对于这种情况,您也需要做类似的事情。
我想写一个 class 环绕 std::thread 并且表现得像 std::thread 但是每次我需要处理一些异步的时候都不会实际分配一个线程。原因是我需要在不允许动态分配的上下文中使用多线程,而且我也不希望有创建 std::thread 的开销。
相反,我希望线程在循环中 运行 并等待它可以开始处理。客户端调用 invoke
唤醒线程。 Thread 锁定一个互斥量,它正在处理并再次入睡。函数 join
的行为类似于 std::thread::join,它通过锁定直到线程释放锁(即再次入睡)。
我想我得到了 class 到 运行 但是由于在多线程方面普遍缺乏经验,我想问一下是否有人可以发现竞争条件或者我使用的方法被认为是 "good style"。例如,我不确定临时锁定互斥量是否是 "join" 线程的一种好方法。
编辑
我发现了另一个竞争条件:当在 invoke
之后直接调用 join
时,线程没有理由已经锁定了互斥量并因此锁定了 join
的调用者,直到线程进入休眠状态。为防止这种情况,我必须为调用计数器添加检查。
页眉
#pragma once
#include <thread>
#include <atomic>
#include <mutex>
class PersistentThread
{
public:
PersistentThread();
~PersistentThread();
// set function to invoke
// locks if thread is currently processing _func
void set(const std::function<void()> &f);
// wakes the thread up to process _func and fall asleep again
// locks if thread is currently processing _func
void invoke();
// mimics std::thread::join
// locks until the thread is finished with it's loop
void join();
private:
// intern thread loop
void loop(bool *initialized);
private:
bool _shutdownRequested{ false };
std::mutex _mutex;
std::unique_ptr<std::thread> _thread;
std::condition_variable _cond;
std::function<void()> _func{ nullptr };
};
源文件
#include "PersistentThread.h"
PersistentThread::PersistentThread()
{
auto lock = std::unique_lock<std::mutex>(_mutex);
bool initialized = false;
_thread = std::make_unique<std::thread>(&PersistentThread::loop, this, &initialized);
// wait until _thread notifies, check bool initialized to prevent spurious wakeups
_cond.wait(lock, [&] {return initialized; });
}
PersistentThread::~PersistentThread()
{
{
std::lock_guard<std::mutex> lock(_mutex);
_func = nullptr;
_shutdownRequested = true;
// wake up and let join
_cond.notify_one();
}
// join thread,
if (_thread->joinable())
{
_thread->join();
}
}
void PersistentThread::set(const std::function<void()>& f)
{
std::lock_guard<std::mutex> lock(_mutex);
this->_func = f;
}
void PersistentThread::invoke()
{
std::lock_guard<std::mutex> lock(_mutex);
_cond.notify_one();
}
void PersistentThread::join()
{
bool joined = false;
while (!joined)
{
std::lock_guard<std::mutex> lock(_mutex);
joined = (_invokeCounter == 0);
}
}
void PersistentThread::loop(bool *initialized)
{
std::unique_lock<std::mutex> lock(_mutex);
*initialized = true;
_cond.notify_one();
while (true)
{
// wait until we get the mutex again
_cond.wait(lock, [this] {return _shutdownRequested || (this->_invokeCounter > 0); });
// shut down if requested
if (_shutdownRequested) return;
// process
if (_func) _func();
_invokeCounter--;
}
}
我不明白你到底想问什么。你用的这个款式不错
使用 bools
并检查单个 routines
会更安全,因为 void
returns 什么都没有,所以你可能会被错误卡住。检查所有你能做的事情,因为线程在后台运行。如果该过程真的成功,请确保调用 运行 正确。你也可以阅读一些关于 "Thread Pooling".
你问的是潜在的竞争条件,我在显示的代码中看到了至少一个竞争条件。
构造了一个PersistentThread
后,不保证新线程会在主执行线程returns之前从构造函数中获取其loop()
中的初始锁,进入invoke()
。有可能主执行线程在构造函数完成后立即进入invoke()
,结果没有通知任何人,因为内部执行线程还没有锁定互斥量。因此,此 invoke()
不会导致任何处理发生。
您需要将构造函数的完成与执行线程的初始锁获取同步。
编辑:您的修改看起来正确;但我还发现了另一个竞争条件。
与documented in the description of wait()一样,wait()
可能会“虚假”醒来。仅仅因为 wait()
返回,并不意味着其他线程已进入 invoke()
.
您需要一个计数器,除此之外,invoke()
递增计数器,执行线程仅在计数器大于零时执行其分配的职责,递减它。这将防止虚假 wake-ups.
我也会让执行线程在输入wait()
之前检查计数器,只有当它为0时才输入wait()
。否则,它递减计数器,执行其功能,然后循环返回。
这应该会堵塞该区域中所有潜在的竞争条件。
P.S。虚假 wake-up 也适用于初始通知,在您的更正中,执行线程已进入循环。对于这种情况,您也需要做类似的事情。