2017-02-21 59 views
3

我想寫一個包裝std :: thread的類,它的行爲像std :: thread,但每次我需要處理某些異步時都沒有實際分配一個線程。原因是我需要在不允許動態分配的上下文中使用多線程,而且我也不想要創建std :: thread的開銷。編寫一個活着的線程

取而代之,我想要一個線程在循環中運行並等待它可以開始處理。客戶呼叫invoke喚醒線程。線程鎖定一個互斥鎖,它是否正在處理並再次入睡。函數join的行爲類似於std :: thread :: join,直到線程釋放鎖(即再次入睡)爲止。

我想我得到了課程,但由於多線程的普遍缺乏經驗,我想問問有沒有人可以發現比賽條件,或者如果我使用的方法被認爲是「好風格」。例如,我不確定是否臨時鎖定互斥體是一種體面的方式來「加入」線程。

編輯 我發現了另一個競爭條件:調用join後直接invoke時,沒有理由線程已經鎖定互斥體,因此直到線程進入睡眠狀態鎖定的join調用者。爲了防止這種情況,我必須爲invoke計數器添加一個檢查。

#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--; 

    } 
} 
+5

聽起來像你想要[線程池](https://en.wikipedia.org/wiki/Thread_pool) – NathanOliver

+0

也許這應該被張貼在Codereview SE? –

+0

@NathanOliver我第二。只需在開始時立即創建一些線程,然後使用它們。 – Carcigenicate

回答

1

您正在詢問潛在的競爭條件,並且我在所示的代碼中看到至少一個競爭條件。

構建PersistentThread後,不能保證新線程將在主執行線程從構造函數返回並進入invoke()之前獲取其loop()中的初始鎖。主執行線程有可能在構造函數完成後立即輸入invoke(),最終通知nobody,因爲內部執行線程尚未鎖定互斥量。因此,這個invoke()將不會導致任何處理髮生。

您需要將構造函數的完成與執行線程的初始鎖獲取同步。

編輯:你的修改看起來正確;但我也發現了另一種競賽狀況。

由於documented in the description of wait()wait()可能會「虛假地」醒來。僅僅因爲wait()返回,並不意味着其他某個線程已輸入invoke()

除了其他所有內容外,您還需要一個計數器,invoke()遞增計數器,執行線程僅在計數器大於零時執行其分配的職責,然後遞減計數器。這將防止虛假的喚醒。

我本來也有執行線程進入wait()前檢查櫃檯,並進入wait()僅當它是0。否則,它遞減計數器,執行其功能,並循環回。

這應該堵塞這個領域所有潛在的競爭條件。

P.S.虛假喚醒還適用於初始通知,在您的更正中,執行線程已進入循環。你也需要爲這種情況做類似的事情。

+0

解決方法是在創建線程之前添加'auto lock = std :: unique_lock (_mutex);'在創建之後添加'_cond.wait(lock);'然後讓線程調用'_cond.notify_one ();'在創建本地鎖後?我也可以編輯問題中的源代碼,... –

+0

添加了我認爲是源代碼中的解決方案 –

+0

是的,但是您的工作尚未完成,編輯... –

0

我不明白你要問什麼。這是你使用的一種很好的風格。

使用bools會更安全並檢查單個routines,因爲void不會返回任何內容,因此您可能會由於錯誤而導致卡住。自線程在引擎蓋下運行後,檢查所有可以使用的內容。如果過程真的成功,請確保呼叫正確運行。你也可以閱讀一些關於「線程池」的東西。

+0

您是否在討論使用bools作爲'_func'的結果類型?我不太確定這將防止有問題的情況。我覺得要抓住例外情況,但這是關於它的。 –

+0

捕捉異常最終會是同樣的感覺,只是更昂貴 – Lazcano

+0

@Lazcano異常是當它們不存在時是零成本的。但是,當他們這樣做時,他們的成本要高得多,但這不應該很重要,因爲這在大多數情況下並不經常發生。 – ProXicT

相關問題