我想编写一个包含std :: thread的类,其行为类似于std :: thread,但每次我需要处理异步时都没有实际分配线程。原因是我需要在我不允许动态分配的上下文中使用多线程,而且我也不想要创建std :: thread的开销。
相反,我希望一个线程在循环中运行并等到它可以开始处理。客户端调用invoke
唤醒线程。线程锁定互斥锁,进行处理并再次入睡。函数join
的行为类似于std :: thread :: join,直到线程释放锁(即再次入睡)。
我认为我上课了,但由于缺乏多线程经验,我想问一下是否有人能够发现竞争条件,或者我使用的方法是否被认为是好的风格&#34 ;。例如,我不确定临时锁定互斥锁是否是一种体面的方式来加入"线程。
修改
我发现了另一种竞争条件:在join
之后直接调用invoke
时,没有理由线程已经锁定了互斥锁,从而锁定了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--;
}
}
答案 0 :(得分:1)
您正在询问潜在的竞争条件,并且我在所显示的代码中看到至少一种竞争条件。
在构造PersistentThread
之后,无法保证在主执行线程从构造函数返回并进入loop()
之前,新线程将在其invoke()
中获取其初始锁。在构造函数完成后,主执行线程可能会立即进入invoke()
,最终会通知任何人,因为内部执行线程还没有锁定互斥锁。因此,此invoke()
不会导致任何处理。
您需要将构造函数的完成与执行线程的初始锁定获取同步。
编辑:您的修订看起来正确;但我也发现了另一种竞争条件。
作为documented in the description of wait(),wait()
可能会被唤醒&#34;虚假地&#34;。仅仅因为wait()
返回,并不意味着某个其他帖子已输入invoke()
。
除了其他所有内容之外,还需要一个计数器,invoke()
递增计数器,执行线程仅在计数器大于零时执行其指定的职责,并递减计数器。这样可以防止虚假的唤醒。
我还要让执行线程在输入wait()
之前检查计数器,并且仅在它为0时输入wait()
。否则,它会递减计数器,执行其功能,并循环回来。
这应该可以填补这一领域的所有潜在竞争条件。
P.S。虚假唤醒也适用于校正中的初始通知,即执行线程已进入循环。你也需要为这种情况做类似的事情。
答案 1 :(得分:0)
我不明白你究竟要问的是什么。这是你用过的好风格。
使用bools
会更加安全并检查单routines
,因为void
没有返回任何内容,因此您可能会被错误导致卡住。检查一切,因为线程在引擎盖下运行。如果进程确实成功,请确保调用正确运行。你也可以阅读一些关于“线程池”的内容。