写一个活着的线程

Writing a thread that stays alive

本文关键字:活着 线程 一个      更新时间:2023-10-16

我想编写一个围绕std :: thread的类,表现得像std ::螺纹,但实际上每次我需要处理某些东西时都没有分配线程。原因是我需要在不允许动态分配的上下文中使用多线程,而且我也不想拥有创建std :: thread的开销。

相反,我希望一个线程在循环中运行并等到它可以开始处理。客户端调用invoke唤醒线程。线程锁定了一个静音,进行了处理,然后再次入睡。一个函数join的行为类似于std :: thread ::通过锁定直到线释放锁(即再次入睡)。

)。

我认为我要参加课程,但是由于多线程缺乏经验,我想问问有人是否可以发现比赛条件,或者我使用的方法是否被认为是"良好的风格"。例如,我不确定临时锁定互斥X是否是"加入"线程的一种体面方法。

编辑我发现了另一个种族条件:当invoke之后直接调用join时,没有理由该线程已经锁定了Mutex,从而锁定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--;
    }
}

您正在询问潜在的比赛条件,我在显示的代码中至少看到一个比赛条件。

构造PersistentThread后,无法保证新线程将在主执行线程从构造函数返回并输入invoke()之前在其loop()中获取其初始锁定。构造函数完成后,主执行线程可能会立即输入invoke(),最终通知任何人,因为内部执行线程尚未锁定MUTEX。因此,此invoke()不会导致进行任何处理。

您需要将构造函数的完成与执行线程的初始锁定获取同步。

编辑:您的修订看起来正确;但是我也发现了另一个种族条件。

如Wait()描述中所记录的,wait()可能醒来。仅仅因为wait()返回,并不意味着其他一些线程已输入invoke()

除其他所有内容外,您还需要一个计数器,而invoke()增加了计数器,并且仅在计数器大于零时执行其分配的职责,而执行线程则将其降低。这将防止虚假唤醒。

i还将执行线程在输入wait() 之前检查计数器,并仅在0时输入wait(),否则,它会降低计数器,执行其函数并循环返回。p>这应该堵塞该区域的所有潜在种族条件。

P.S。伪造的唤醒还适用于您的校正中的初始通知,即执行线程已输入循环。您也需要在这种情况下做类似的事情。

我不明白您要准确地问什么。这是您使用的不错的样式。

使用bools会更安全并检查单个routines,因为void毫无疑问,因此您可能会被错误造成。由于线程在引擎盖下运行,因此检查所有可能的东西。如果该过程真正成功,请确保通话正确。另外,您可以阅读一些有关"线程池"的内容。