2017-03-18 9 views
1

2つのスレッドで2つのものを無限ループで計算したいのですが、それらの2つはお互いに依存していません。同じスレッドをループで作って、同じミューテックスを何度も何度も繰り返さないようにしてください。

また、私は無限ループでも端末に出力したい(最後の出力が計算されるたびに再起動し、間に何かのステップは必要ない)。したがって、私はすべて安全な状態にあるグローバル変数(最初の計算の2回の連続した反復と2番目の計算の1つの値)のローカルコピーをいくつか求めます。

問題は、func1から何らかの出力を得るために2つの計算にいくつかの睡眠を追加する必要があることです。私はCPU使用量を確認し、睡眠は間違いなくそれを下げます。 どうすればこの問題を回避できますか?

また、テストのためのコメントを参照してください:

#include <iostream> 
#include <thread> 
#include <condition_variable> 
#include <mutex> 

int n(0); 
long s(1234); 
double t(56.78); 

std::condition_variable cond; 
std::mutex m1; 
std::mutex m2; 

void func1() { 
    while (true) { 
     // Not needed due to wait unlocking the mutex 
     //std::this_thread::sleep_for(std::chrono::nanoseconds(1)); 

     std::unique_lock<std::mutex> lck1(m1); 
     cond.wait(lck1); 
     int n1(n); 
     long s1(s); 
     cond.wait(lck1); 
     int n2(n); 
     long s2(s); 
     lck1.unlock(); 

     std::unique_lock<std::mutex> lck2(m2); 
     double ti(t); 
     lck2.unlock(); 

     // calculate and print some output 
     std::this_thread::sleep_for(std::chrono::milliseconds((n1*n2)/2)); 
     std::cout << n1 << ":" << s1 << ", " << n2 << ":" << s2 << ", " << ti << std::endl; 
    } 
} 

void func2() { 
    while (true) { 
     // Why do I need this to make func1 ever proceed (ok, really seldom and random func1 got the mutex) and 
     // how to work around this without sleep lowering time for computations? 
     std::this_thread::sleep_for(std::chrono::nanoseconds(1)); // comment out to test 

     std::unique_lock<std::mutex> lck1(m1); 

     n++; 

     // do some stuff taking some time with s 
     std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3)); 

     cond.notify_all(); 
    } 
} 

void func3() { 
    while (true) { 
     // Why do I need this to make func1 ever proceed (it got the mutex never ever) and 
     // how to work around this without sleep lowering time for computations? 
     std::this_thread::sleep_for(std::chrono::nanoseconds(1)); // comment out to test 

     std::unique_lock<std::mutex> lck2(m2); 
     // do something taking some time with t 
     std::this_thread::sleep_for(std::chrono::seconds(2)); 
    } 
} 

int main() { 

    std::thread t1(func1); 
    std::thread t2(func2); 
    std::thread t3(func3); 

    t1.join(); 
    t2.join(); 
    t3.join(); 

    return 0; 
} 
+0

t3でt1とt2をトリガーし、次にt1とt2でt3をトリガーするように見えます。 4つのミューテックスを使うことができます.t1にはm1、t2にはm2、t3にはm31、m32があります。 t1はm1を待って作業を行い、m31を解放します。 t1はm2を待って作業を行い、m32を解放します。 t3はm31からm32を待ち、作業を行い、m1とm2を解放します。 – rcgldr

+0

あなたの問題を再現する実際のビルド可能な実行可能コードを投稿してください。 [mcve]の投稿について読む。 –

+0

@rcgldrこのアイデアは素晴らしいですが、2つの連続した値が得られるとは限りません。あるいは、私はちょうどそれが間違っている。 –

答えて

0

あなたがして睡眠を置き換えることができます。これは、他のスレッドにロックを取得する機会を与える

std::this_thread::yield(); 

http://en.cppreference.com/w/cpp/thread/yield

+0

仕様を読んで、これは私が必要とするものですが、どういうわけかスリープ時には動作しません。また、関数呼び出し(実際の睡眠よりも長い時間がかかっても)が必要なので、実際の違いはありません。しかし、それはそれがより効率的にすることは決して可能ではありません。他の待機中のスレッドが同じロックに先立ってロックを取得することを保証するものが、完璧になるでしょう。 –

0

あなたは条件変数なしでこれを行うことができます。

long s[2] = {1234,1234}; // instead of a single value 


void func1() { 
    while (true) { 
     std::unique_lock<std::mutex> lck1(m1); 
     int n1(n); 
     long s1(s[0]); 
     long s2(s[1]); 
     lck1.unlock(); 

     int n2 = n1 + 1; 
// ...rest of func1 is unchanged... 


void func2() { 
    while (true) { 
     std::unique_lock<std::mutex> lck1(m1); 
     n++; 
     // compute s[0] 
     std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3)); 
     n++; 
     // compute s[1] 
     std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3)); 

     std::this_thread::yield(); 
    } 
} 
+0

テスト済みです。非常に奇妙な動作をしています。ときどき1行しか印刷しない(おそらくfunc1に戻って来ないかもしれません)か、または変更せずに無限に同じ最初の行を印刷します(おそらくfunc2に戻ることはないでしょう)。さらに、必要なストレージを2倍にして、スワップ速度が速すぎないスロー速度に注意を払う必要があります。 –

1

まず、あなたはwait通話が実際に待っていないという条件変数を仮定しなければなりません。有効な(ただし極端な)実装はlk.unlock(); lk.lock();であり、 "偽の起床"のためにそのように動作することがあります。条件を条件変数に関連付ける必要があります。条件が満たされない場合はループします。

第2に、ミューテックスのロックを保持している状態変数に通知すると、待機中のスレッドは通知から復帰し、mutexをブロックするだけであるため、通知スレッドは待機中のスレッドチャンスを得る。

したがって、条件変数を通知する前にmutexのロックを解除する方が良いです。

lck1.unlock(); 
cond.notify_all(); 

これは、通知スレッドがロックを再取得する前に、待機中のスレッドにすぐにミューテックスを取得する機会を与えます。

反復の間にstd::this_thread::yield()呼び出しを追加して、他のスレッドにロックを取得するチャンスを与えることができます。しかし、これは理想的ではありません---スケジューラを混乱させるために明示的に何かを行う必要のあるコードは、書き換えの対象です。

+0

偽の目覚めのヒントをありがとう!しかし、私はすでに通知の前にロックを解除しており、それは全く違いはありませんでした。 –

関連する問題