2016-08-31 16 views
2


私は多くのミスがC++ 11で同時実行を学ぶために作るしようとしています。

ここでは、これは次のようなものです: 1つのキューと3つのスレッドは、キューに整数を入れると仮定し、他の2つはそれに対応してs1、s2を増やすと仮定します。待ち行列にあった総数を得ることができるように待ち行列をポップします。簡単にするために、1〜10の数字をキューに入れます。

しかし、時にはそれが動作し、時にはそれが無限ループがあるように思える::どのような理由でしょうか?

C++ 11 condtional変数

#include <queue> 
#include <memory> 
#include <mutex> 
#include <thread> 
#include <iostream> 
#include <condition_variable> 
#include <string> 

class threadsafe_queue { 
private: 
    mutable std::mutex mut; 
    std::queue<int> data_queue; 
    std::condition_variable data_cond; 
    std::string log; //just to see what is going on behind 
    bool done; 

public: 
    threadsafe_queue(){ 
     log = "initializing queue\n"; 
     done = false; 
    } 
    threadsafe_queue(threadsafe_queue const& other) { 
     std::lock_guard<std::mutex> lk(other.mut); 
     data_queue = other.data_queue; 
    } 
    void set_done(bool const s) { 
     std::lock_guard<std::mutex> lk(mut); 
     done = s; 
    } 
    bool get_done() { 
     std::lock_guard<std::mutex> lk(mut); 
     return done; 
    } 
    void push(int new_value) { 
     std::lock_guard<std::mutex> lk(mut); 
     log += "+pushing " + std::to_string(new_value) + "\n"; 
     data_queue.push(new_value); 
     data_cond.notify_one(); 
    } 
    void wait_and_pop(int& value) { 
     std::unique_lock<std::mutex> lk(mut); 
     data_cond.wait(lk, [this]{return !data_queue.empty();}); 
     value = data_queue.front(); 
     log += "-poping " + std::to_string(value) + "\n"; 
     data_queue.pop(); 
    } 
    std::shared_ptr<int> wait_and_pop() { 
     std::unique_lock<std::mutex> lk(mut); 
     data_cond.wait(lk, [this]{return !data_queue.empty();}); 
     std::shared_ptr<int> res(std::make_shared<int>(data_queue.front())); 
     log += "- popping " + std::to_string(*res) + "\n"; 
     data_queue.pop(); 
     return res; 
    } 
    bool try_pop(int& value) { 
     std::lock_guard<std::mutex> lk(mut); 
     if (data_queue.empty()) { 
      log += "tried to pop but it was empty\n"; 
      return false; 
     } 
     value = data_queue.front(); 
     log += "-popping " + std::to_string(value) + "\n"; 
     data_queue.pop(); 
     return true; 
    } 
    std::shared_ptr<int> try_pop() { 
     std::lock_guard<std::mutex> lk(mut); 
     if (data_queue.empty()) { 
      log += "tried to pop but it was empty\n"; 
      return std::shared_ptr<int>(); 
     } 
     std::shared_ptr<int> res(std::make_shared<int>(data_queue.front())); 
     log += "-popping " + std::to_string(*res) + "\n"; 
     data_queue.pop(); 
     return res; 
    } 
    bool empty() const { 
     std::lock_guard<std::mutex> lk(mut); 
     //log += "checking the queue if it is empty\n"; 
     return data_queue.empty(); 
    } 

    std::string get_log() { 
     return log; 
    } 

}; 

threadsafe_queue tq; 
int s1, s2; 

void prepare() { 
    for (int i = 1; i <= 10; i++) 
     tq.push(i); 
    tq.set_done(true); 
} 

void p1() { 
    while (true) { 
     int data; 
     tq.wait_and_pop(data); 
     s1 += data; 
     if (tq.get_done() && tq.empty()) break; 
    } 
} 

void p2() { 
    while (true) { 
     int data; 
     tq.wait_and_pop(data); 
     s2 += data; 
     if (tq.get_done() && tq.empty()) break; 
    } 
} 

int main(int argc, char *argv[]) { 
    std::thread pp(prepare); 
    std::thread worker(p1); 
    std::thread worker2(p2); 
    pp.join(); 
    worker.join(); 
    worker2.join(); 

    std::cout << tq.get_log() << std::endl; 
    std::cout << s1 << " " << s2 << std::endl; 
    return 0; 
} 
+0

'done'を設定するときに' data_cond'に通知し、 'data_cond.wait'条件でそれをチェックする必要があります。 – dewaffled

+0

'push()'の前に 'wait_and_pop()'が呼び出された場合、 'push()'はその待ちから 'wait_and_pop()'を解放するために 'notify_one'を呼び出すためにmutexを通過できないため、私はmutexロックを取得する前に 'wait()' *すべきだと思います。 –

+0

@ KenY-N、それは私が疑っていたものです。しかし、私はpush()が何よりも先に動作することを確実にするにはどうすればよいですか? – Nano

答えて

1

(tq.get_done()& & tq.empty())ブレークであれば、関数P1ライン5

を見。

それが空であったのであれば、あなたはキューをチェックします。そうではありませんでした。今すぐループして入力する

tq.wait_and_pop(data);あなたは

data_cond.wait({!返すdata_queue.empty();} LK、[この])を見つけることができます

。 (data_queue.empty()){ ウェイト(LK)しながら、本質的

ある

。 }

「!」がありません。

今、あなたのスレッドが座っていると、キューがないプロデューサーIDがキューを埋める行っているので、決して起こらないであろう、空であることを待ちます。スレッドは決して参加しません。

これを修正する方法はたくさんあります。私はあなた自身で1つを見つけると確信しています。

+0

ああ、それも本当です。 'p2'が' p1'の 'tq.empty()'と次の 'tq.wait_and_pop(data);の間のキューを空にすると、別のハングが発生します。 –

+0

cool!ありがとうございました – Nano

+0

@Nano:私の答えがあなたの質問を満たしていれば、私の答えのすぐ隣に緑色のちょっとしたチェックを付けてください。タイ – PanicSheep