web-dev-qa-db-ja.com

std :: lock(c ++ 11)を使用した大量のCPU負荷

スレッド/ミューテックスマネージャーを実装するための最近の取り組みは、75%のCPU負荷(4コア)になりましたが、実行中の4つのスレッドはすべてスリープ状態か、ミューテックスビーのロックが解除されるのを待っていました。

特定のクラスは、ここに完全に掲載するには大きすぎますが、原因を2つのミューテックスのデッドロックセーフな取得に絞り込むことができます。

_std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );
std::lock( lock1, lock2 );
_

クラスの別の部分では、_std::condition_variable_と_mutex1_のwait()およびnotify_one()を使用して、一部のコードを同時に選択的に実行します。

への簡単な変更

_std::unique_lock<std::mutex> lock1( mutex1 );
std::unique_lock<std::mutex> lock2( mutex2 );
_

cPU使用率を通常の1〜2%に下げました。

私は、std::lock()関数はそれほど非効率的だと信じています。これはg ++ 4.6.3のバグでしょうか?

編集:(例)

_#include <atomic>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mutex1, mutex2;
std::condition_variable cond_var;

bool cond = false;
std::atomic<bool>done{false};

using namespace std::chrono_literals;

void Take_Locks()
    {
    while( !done )
        {
        std::this_thread::sleep_for( 1s );

        std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
        std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );
        std::lock( lock1, lock2 );

        std::this_thread::sleep_for( 1s );
        lock1.unlock();
        lock2.unlock();
        }
    }

void Conditional_Code()
    {
    std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
    std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );

    std::lock( lock1, lock2 );
    std::cout << "t4: waiting \n";

    while( !cond )
        cond_var.wait( lock1 );

    std::cout << "t4: condition met \n";
    }

int main()
    {
    std::thread t1( Take_Locks ), t2( Take_Locks ), t3( Take_Locks );
    std::thread t4( Conditional_Code );

    std::cout << "threads started \n";
    std::this_thread::sleep_for( 10s );

    std::unique_lock<std::mutex> lock1( mutex1 );
    std::cout << "mutex1 locked \n" ;
    std::this_thread::sleep_for( 5s );

    std::cout << "setting condition/notify \n";
    cond = true;
    cond_var.notify_one();
    std::this_thread::sleep_for( 5s );

    lock1.unlock();
    std::cout << "mutex1 unlocked \n";
    std::this_thread::sleep_for( 6s );

    done = true;
    t4.join(); t3.join(); t2.join(); t1.join();
    }
_
31
mac

私のマシンでは、ほとんどの場合、スレッドがスリープしているか、ロックされたミューテックスでブロックされているため、次のコードは1秒間に10回出力され、ほぼ0CPUを消費します。

_#include <chrono>
#include <thread>
#include <mutex>
#include <iostream>

using namespace std::chrono_literals;

std::mutex m1;
std::mutex m2;

void
f1()
{
    while (true)
    {
        std::unique_lock<std::mutex> l1(m1, std::defer_lock);
        std::unique_lock<std::mutex> l2(m2, std::defer_lock);
        std::lock(l1, l2);
        std::cout << "f1 has the two locks\n";
        std::this_thread::sleep_for(100ms);
    }
}

void
f2()
{
    while (true)
    {
        std::unique_lock<std::mutex> l2(m2, std::defer_lock);
        std::unique_lock<std::mutex> l1(m1, std::defer_lock);
        std::lock(l2, l1);
        std::cout << "f2 has the two locks\n";
        std::this_thread::sleep_for(100ms);
    }
}

int main()
{
    std::thread t1(f1);
    std::thread t2(f2);
    t1.join();
    t2.join();
}
_

サンプル出力:

_f1 has the two locks
f2 has the two locks
f1 has the two locks
...
_

これをOSXで実行していますが、アクティビティモニターアプリケーションは、このプロセスが0.1%のCPUを使用していると言っています。マシンはIntelCore i5(4コア)です。

ライブロックまたは過度のCPU使用率を作成するために、この実験を調整できてうれしいです。

更新

このプログラムがプラットフォームで過剰なCPUを使用している場合は、代わりに::lock()を呼び出すように変更してみてください。これは、次のように定義されています。

_template <class L0, class L1>
void
lock(L0& l0, L1& l1)
{
    while (true)
    {
        {
            std::unique_lock<L0> u0(l0);
            if (l1.try_lock())
            {
                u0.release();
                break;
            }
        }
        std::this_thread::yield();
        {
            std::unique_lock<L1> u1(l1);
            if (l0.try_lock())
            {
                u1.release();
                break;
            }
        }
        std::this_thread::yield();
    }
}
_

それがあなたに何か違いをもたらしたかどうか知りたいと思います、ありがとう。

アップデート2

長い遅れの後、私はこの主題に関する論文の最初の草稿を書きました。このホワイトペーパーでは、この仕事を遂行するための4つの異なる方法を比較しています。コピーして自分のコードに貼り付けて自分でテストできるソフトウェアが含まれています(見つけたものを報告してください!):

http://howardhinnant.github.io/dining_philosophers.html

25
Howard Hinnant

ドキュメントにあるように、 [t]オブジェクトは、lock、try_lock、unlockの不特定の一連の呼び出しによってロックされます。ミューテックスが他のスレッドによってかなりの期間保持されている場合、おそらく効率的な方法はありません。関数が回転せずに待機できる方法はありません。

6
David Schwartz

std::lock()非メンバー関数mayは、ライブロックの問題またはパフォーマンスの低下を引き起こす可能性があります。保証は "デッドロックしない "。

複数のミューテックスの「ロック順序(ロック階層)」を設計で決定できる場合は、汎用のstd::lock()を使用せずに、各ミューテックスを事前に決定された順序でロックすることをお勧めします。

詳細については、 デッドロックなしで複数のロックを取得する を参照してください。

6
yohjp

まず、すべての回答に感謝したいと思います。

効果を再現したサンプルコードの作成中に、トラブルの原因を突き止めました。

条件付き部分は両方のミューテックスをロックしますが、std::condition_variable::wait()関数には1つだけを使用します。

しかし、それでも、このような高いCPU負荷を生み出す舞台裏で何が起こっているのか疑問に思います。

0
mac