2017-05-08 63 views
2

我有一個程序將10個線程放到一個向量中,每個向量應該在完成前打印出一個字符5次('A'代表第一個線程,'B'爲第二個,等等)。我可以讓它們一次運行(使用detach())或讓它們一次運行一個(使用join())。現在我想用一個Mutex來限制每次允許打印的線程數爲2.我已經能夠聲明互斥鎖並將鎖定到位,但我不確定如何應用這樣的限制。任何人有任何想法如何進行?使用互斥鎖限制每次運行的線程數爲2

deque<int> q ; 
mutex print_mutex ; 
mutex queue_mutex ; 
condition_variable queue_cond ; 

void begin(int num) { 
    unique_lock<mutex> ul {queue_mutex}; 
    q.emplace_back(num); 
    queue_cond.wait(ul,[num]{ 
     return q.front() == num; }); 
    q.pop_front(); 
    cout << num << " leaves begin " << endl ; 
} 

void end (int num) { 
    lock_guard<mutex>lg{queue_mutex}; 
    queue_cond.notify_all(); 
    cout << num << " has ended " << endl ; 
} 

void run(int num, char ch) { 
    begin(num); 
    for (int i = 0; i < 5; ++i) { 
     { 
      lock_guard<mutex> lg { print_mutex }; 
      cout << ch << endl << flush ; 
     } 
     sleep_for(milliseconds(250)); 
    } 
    end(num); 
} 

int main() { 
    vector<thread>threads {}; 
    for (int i = 0; i < 10; ++i) { 
     threads.push_back(thread{run,i,static_cast<char>(65+i)}); 
     threads.at(i).join(); 
    } 
} 
+1

你需要一個整數來計算正在運行的線程的數量。 –

+1

使用'detach()'和'join()'來控制排序是錯誤的。 'main'中的'for'循環只是串行化線程;這是毫無意義的。使用一個循環來創建全部**的線程,然後是一個單獨的循環,用於連接全部**的線程。然後找出你想如何交互的線程。不要使用'detach()';這絕對不是你需要的。 –

+0

@PeteBecker非常棒,那是我的問題!優秀的解釋。我試圖添加一個更好的鎖定條件,但沒有任何更改,直到我將連接分隔到另一個循環。如果你想繼續,並作爲答案張貼,我會很樂意接受它。 – gmooney8

回答

3

您已經爲全局deque<int> q設置了一個FIFO。所以讓我們使用它。

目前,您正試圖限制執行,直到當前線程位於前面。雖然有一個錯誤,因爲begin會立即從雙端隊列中彈出該線程。當您致電end時,最好刪除該值。下面是變化,第一:

void end(int num) 
{ 
    { 
     lock_guard<mutex>lg{queue_mutex}; 
     cout << num << " has ended " << endl ; 
     q.erase(find(q.begin(), q.end(), num)); 
    } 
    queue_cond.notify_all(); 
} 

這使用std::find<algorithm>刪除特定值。您可以使用pop_front,但我們即將更改該邏輯,以便更通用。另請注意,通知時不需要鎖定條件變量。

因此,將begin中的邏輯擴展到前兩位並沒有多大的延伸。在這裏:

void begin(int num) 
{ 
    unique_lock<mutex> ul {queue_mutex}; 
    q.emplace_back(num); 
    queue_cond.wait(ul,[num]{ 
     auto end = q.begin() + std::min(2, static_cast<int>(q.size())); 
     return find(q.begin(), end, num) != end; 
     }); 
    cout << num << " leaves begin " << endl ; 
} 

可以更改2到任何你想要的,最多允許多個線程通過。在某些時候,您可能會放棄這種方法,並使用一個簡單的方法,如單個計數器變量,然後依靠線程調度器來管理哪個線程被喚醒,而不是強制它們進入FIFO。這樣你就可以切換到使用notify_one來喚醒單線程並減少開銷開銷。

無論如何,最後要做的就是從線程生成循環中刪除join。並發現在由beginend管理。所以你應該這樣做:

for (int i = 0; i < 10; ++i) { 
    threads.push_back(thread{run, i, 'A'+i}); 
} 
for (auto & t : threads) t.join(); 
+0

很好的解釋,稻田!我最終找到了一個不同的解決方案(使用你提到的計數器),但你的解釋更好地理解它。謝謝! – gmooney8

+0

我忘記提到的一件事是你的開始/結束代碼不是異常安全的。如果你的線程因異常而終止,那麼'end'永遠不會被調用。你可能會考慮在對象中包裝開始/結束調用,以便RAII處理它。 – paddy