编写一个保持活动状态的线程

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 也适用于初始通知,在您的更正中,执行线程已进入循环。对于这种情况,您也需要做类似的事情。