编写一个保持活动的线程

时间:2017-02-21 13:01:11

标签: c++ multithreading locking condition-variable

我想编写一个包含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--;

    }
}

2 个答案:

答案 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没有返回任何内容,因此您可能会被错误导致卡住。检查一切,因为线程在引擎盖下运行。如果进程确实成功,请确保调用正确运行。你也可以阅读一些关于“线程池”的内容。