ミューテックスの例/チュートリアル?[閉まっている]


176

私はマルチスレッドに不慣れで、ミューテックスの仕組みを理解しようとしていました。多くのグーグル操作を行いましたが、ロックが機能しない独自のプログラムを作成したため、どのように機能するかについて疑問が残りました。

mutexの絶対に直感的でない構文の1つはですpthread_mutex_lock( &mutex1 );。ここでは、mutexがロックされているように見えますが、私が本当にロックしたいのは他の変数です。この構文は、ミューテックスをロックすると、ミューテックスがロック解除されるまでコードの領域をロックすることを意味しますか?次に、スレッドは領域がロックされていることをどのようにして知るのでしょうか。[ 更新:スレッドは、 メモリフェンシングによって領域がロックされていることを認識しています ]。そして、そのような現象はクリティカルセクションと呼ばれるべきではないでしょうか?[ 更新:重要なセクションオブジェクトはWindowsでのみ使用できます。オブジェクトはミューテックスよりも高速で、それを実装するスレッドにのみ表示されます。それ以外の場合、クリティカルセクションは、ミューテックスによって保護されているコードの領域を指します ]

簡単に言うと、最も簡単なミューテックスのサンプルプログラムと、それがどのように機能するかについてのロジックに関する可能な限り簡単な説明を手伝っていただけませんか?これは他の多くの初心者を助けると確信しています。


2
混乱の例:1.簡単なチュートリアル(それは、スレッド、TBBやpthreadsを高めること)の必要性について強調し続けstackoverflow.com/questions/3528877/... 2. stackoverflow.com/questions/2979525/... 3。stackoverflow.com/questions/2095977/to-mutex-or-not-to-mutex 4. stackoverflow.com/questions/3931026/... 5. stackoverflow.com/questions/1525189/...
ナビ

1
これは不快な意味ではありませんが、最後のコメントが示唆するのは、ミューテックスがどのように機能するのか、なぜそれらが必要なのかについての類似性と技術的説明は少なくて済むということです。
San Jacinto

@San:違反はありません:)私のコメントは、初心者がmutexの最も短く明確な説明を取得できることを示唆することのみを目的としています。多くのアナロジーは初心者を混乱させる可能性があるため、異なるアナロジーは個別に保管する必要があります。質問と回答を投稿した理由は、初心者として、長い説明とコードサンプルを読むのが面倒だったからです。他の人に苦痛を経験してほしくない。
ナビ

2
@Cory:この答えが改善される可能性がある場合、私はあなたの提案を喜んで受けたいと思います。他の多くの人が参考にしてくれて嬉しいです。それでも問題が解決しない場合は、他のミューテックスチュートリアルを指摘した他の人々からの回答もあります。なぜそんなにネガティブなの?
Navの

回答:


278

これは、世界中の初心者にコンセプトを説明する私の謙虚な試みです:(私のブログでも色分けされたバージョン

多くの人が、愛する人と話すために、1つの電話ブース(携帯電話を持っていません)に走ります。ブースのドアハンドルを最初に掴むのは、電話の使用を許可された人です。彼は電話を使用している間はドアのハンドルを握り続けなければなりません。そうしないと、誰かがハンドルをつかんで投げ出し、妻と話します:)そのようなキューシステムはありません。通話が終了し、ブースから出てドアハンドルを離れると、次にドアハンドルを握った人が電話を使用できるようになります。

スレッドは、次のとおりです。人それぞれミューテックスがある:ドアハンドルロックがある:人の手のリソースは次のとおりです。電話


同時に他のスレッドによって変更されてはならないコード行を実行する必要があるスレッド(電話を使用して妻と話す)は、最初にミューテックスのロックを取得する必要があります(ブースのドアハンドルを握ります) )。その後でのみ、スレッドはそれらのコード行を実行できます(電話をかけることができます)。

スレッドがそのコードを実行すると、ミューテックスのロックを解除して、別のスレッドがミューテックスのロックを取得できるようにする必要があります(他のユーザーは電話ボックスにアクセスできます)。

[ ミューテックスを持つという概念は、実際の排他的アクセスを考えると少しばかげていますが、プログラミングの世界では、他のスレッドにスレッドがすでにコードのいくつかの行を実行していることを「見える」ようにする他の方法はないと思います。再帰的なミューテックスなどの概念がありますが、この例は基本的な概念を示すことだけを目的としています。この例がコンセプトの明確な画像を提供してくれることを願っています。]

C ++ 11スレッドの場合:

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

std::mutex m;//you can use std::lock_guard if you want to be exception safe
int i = 0;

void makeACallFromPhoneBooth() 
{
    m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside
      //man happily talks to his wife from now....
      std::cout << i << " Hello Wife" << std::endl;
      i++;//no other thread can access variable i until m.unlock() is called
      //...until now, with no interruption from other men
    m.unlock();//man lets go of the door handle and unlocks the door
}

int main() 
{
    //This is the main crowd of people uninterested in making a phone call

    //man1 leaves the crowd to go to the phone booth
    std::thread man1(makeACallFromPhoneBooth);
    //Although man2 appears to start second, there's a good chance he might
    //reach the phone booth before man1
    std::thread man2(makeACallFromPhoneBooth);
    //And hey, man3 also joined the race to the booth
    std::thread man3(makeACallFromPhoneBooth);

    man1.join();//man1 finished his phone call and joins the crowd
    man2.join();//man2 finished his phone call and joins the crowd
    man3.join();//man3 finished his phone call and joins the crowd
    return 0;
}

コンパイルして使用 g++ -std=c++0x -pthread -o thread thread.cpp;./thread

lockand を明示的に使用する代わりに、スコープロック使用して利点を提供する場合は、ここ示すようにunlock角かっこ使用できます。ただし、スコープ付きロックでは、パフォーマンスのオーバーヘッドが若干発生します。


2
@ San:私は正直になります。はい、私はあなたが完全な初心者に(フローで)詳細を説明するために最善を尽くしたという事実が好きです。しかし、(私を誤解しないでください)この投稿の意図は、概念を短い説明に含めることでした(他の答えが長いチュートリアルを指し示している)。解答全体をコピーして別の解答として投稿してもらえますか。答えをロールバックして編集して、あなたの答えを指すようにします。
ナビ

2
@Tomその場合、そのミューテックスにアクセスするべきではありません。その上での操作はカプセル化する必要があります。これにより、保護されているものはすべて、そのようなあざけりから保護されます。ライブラリの公開APIを使用する場合、ライブラリがスレッドセーフであることが保証されている場合は、明確に異なるミューテックスを含めて、独自の共有アイテムを保護することが安全です。そうでなければ、あなたが提案したように、あなたは確かに新しいドアハンドルを追加しています。
San Jacinto

2
私の要点を広げるために、あなたがしたいことは、ブースの周りに別のより大きな部屋を追加することです。部屋にはトイレとシャワーが含まれている場合もあります。部屋には一度に1人しか入室できないとしましょう。この部屋は、電話ブースのように部屋への入室を保護するハンドル付きのドアが必要になるように部屋を設計する必要があります。したがって、追加のミューテックスがあっても、どのプロジェクトでも電話ボックスを再利用できます。別のオプションは、部屋の各デバイスのロックメカニズムを公開し、部屋クラスのロックを管理することです。どちらの方法でも、同じオブジェクトに新しいロックを追加することはできません。
San Jacinto

8
C ++ 11スレッドの例は間違っています。TBBもそうですが、手がかりは名前付きスコープロックにあります
Jonathan Wakely、2015年

3
@Jonathan、私は両方をよく知っています。あなたは私が書いた文章を逃したよう(could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearerです。スコープ付きロックの使用については、開発しているアプリケーションの種類に応じて、開発者次第です。この回答は、ミューテックスの概念の基本的な理解に対処するためのものであり、その複雑さをすべて理解するためのものではないため、コメントとリンクは歓迎しますが、このチュートリアルの範囲外です。
ナビゲーション

41

ミューテックスは他の問題を解決するために使用できますが、それらが存在する主な理由は相互排除を提供し、それによって競合状態として知られているものを解決することです。2つ(またはそれ以上)のスレッドまたはプロセスが同じ変数に同時にアクセスしようとすると、競合状態になる可能性があります。次のコードを検討してください

//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
  i++;
}

この関数の内部はとてもシンプルに見えます。それはたった1つのステートメントです。ただし、同等の典型的な疑似アセンブリ言語は次のようになります。

load i from memory into a register
add 1 to i
store i back into memory

iでインクリメント操作を実行するには、同等のアセンブリ言語命令がすべて必要なので、iのインクリメントは非アトミック操作であると言います。アトミック操作は、命令の実行が開始されたら中断されないという保証でハードウェア上で完了することができる操作です。iの増分は、3つのアトミック命令のチェーンで構成されます。複数のスレッドが関数を呼び出している並行システムでは、スレッドが誤ったタイミングで読み取りまたは書き込みを行うと問題が発生します。2つのスレッドが同時に実行され、1つが次の関数をすぐに呼び出すとします。また、0に初期化したとしましょう。また、十分なレジスタがあり、2つのスレッドが完全に異なるレジスタを使用しているため、衝突は発生しないと仮定します。これらのイベントの実際のタイミングは次のとおりです。

thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1

何が起こったかというと、2つのスレッドがiを同時にインクリメントしているため、関数が2回呼び出されますが、結果はその事実と一致しません。関数が一度だけ呼び出されたようです。これは、アトミック性がマシンレベルで「壊れている」ためです。つまり、スレッドが互いに割り込んだり、間違ったタイミングで連携したりする可能性があります。

これを解決するメカニズムが必要です。上記の手順にいくつかの順序を課す必要があります。一般的なメカニズムの1つは、1つを除くすべてのスレッドをブロックすることです。Pthread mutexはこのメカニズムを使用します。

(電話を使用して妻と話すことにより)同時に他のスレッドによる共有値を安全に変更できないコード行を実行する必要があるスレッドは、最初にミューテックスのロックを取得する必要があります。このように、共有データへのアクセスを必要とするスレッドは、ミューテックスロックを通過する必要があります。そうして初めて、スレッドはコードを実行できるようになります。このコードセクションは、クリティカルセクションと呼ばれます。

スレッドがクリティカルセクションを実行すると、別のスレッドがミューテックスのロックを取得できるように、ミューテックスのロックを解放する必要があります。

ミューテックスを持つという概念は、実際の物理オブジェクトへの排他的アクセスを求める人間を考えると少し奇妙に見えますが、プログラミングするときは意図的にする必要があります。並行スレッドとプロセスには、私たちが行うような社会的および文化的な育成がないため、データを適切に共有するように強制する必要があります。

技術的に言えば、mutexはどのように機能するのでしょうか。以前に述べたのと同じ競合状態に悩まされていませんか?pthread_mutex_lock()は、変数を単純にインクリメントするよりも少し複雑ではありませんか?

技術的に言えば、私たちを助けるためにいくつかのハードウェアサポートが必要です。ハードウェア設計者は、複数のことを行うがアトミックであることが保証されている機械命令を提供します。このような命令の典型的な例は、テストアンドセット(TAS)です。リソースのロックを取得しようとするとき、TASを使用してメモリ内の値が0かどうかを確認することがあります。0の場合、それはリソースが使用中で何もしない(またはより正確に)というシグナルになります。 、何らかのメカニズムで待機します。pthreadsミューテックスにより、オペレーティングシステムの特別なキューに入れられ、リソースが利用可能になったときに通知されます。Dumberシステムでは、タイトなスピンループを実行して、条件を繰り返しテストする必要がある場合があります) 。メモリ内の値が0でない場合、TASは他の命令を使用せずに場所を0以外に設定します。それ' 2つのアセンブリ命令を1つに組み合わせて原子性を与えるようなものです。したがって、値のテストと変更(変更が適切な場合)は、いったん開始すると中断できません。そのような命令の上にミューテックスを構築することができます。

注:一部のセクションは、以前の回答と同様に表示される場合があります。私は彼の編集への招待を受け入れました、彼はそれが元の方法を好んだので、私は彼の言葉遣いの少しが注入された私が持っていたものを保持しています。


1
サンさん、どうもありがとうございました。私はあなたの回答にリンクしました:)実際、私はあなたが私の回答+あなたの回答を受け取り、別の回答として投稿して、流れを保つことを意図していました。私の回答の一部を再利用してもかまいません。とにかく、私たち自身でこれを行っているわけではありません。
ナビ

13

私が知っている最高のスレッドのチュートリアルはここにあります:

https://computing.llnl.gov/tutorials/pthreads/

特定の実装ではなく、APIについて書かれているのが好きで、同期を理解するのに役立つ簡単な例がいくつか示されています。


私はそれが間違いなく優れたチュートリアルであることに同意しますが、それは単一のページ上の多くの情報であり、プログラムは長いです。私が投稿した質問は「夢があります」スピーチのミューテックス版です。初心者はミューテックスについて学び、非直感的な構文がどのように機能するかを理解する簡単な方法を見つけます(これはすべてのチュートリアルに欠けている1つの説明です) 。
ナビゲーション

7

私は最近この投稿に遭遇し、標準ライブラリのc ++ 11ミューテックス(つまりstd :: mutex)の更新されたソリューションが必要だと思います。

以下のコードを貼り付けました(ミューテックスでの最初のステップ-win32でHANDLE、SetEvent、WaitForMultipleObjectsなどの同時実行を学びました)。

std :: mutexと友達との最初の試みなので、コメント、提案、改善をぜひ見たいです。

#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <thread>
#include <queue>
#include <chrono>
#include <iostream>


int _tmain(int argc, _TCHAR* argv[])
{   
    // these vars are shared among the following threads
    std::queue<unsigned int>    nNumbers;

    std::mutex                  mtxQueue;
    std::condition_variable     cvQueue;
    bool                        m_bQueueLocked = false;

    std::mutex                  mtxQuit;
    std::condition_variable     cvQuit;
    bool                        m_bQuit = false;


    std::thread thrQuit(
        [&]()
        {
            using namespace std;            

            this_thread::sleep_for(chrono::seconds(5));

            // set event by setting the bool variable to true
            // then notifying via the condition variable
            m_bQuit = true;
            cvQuit.notify_all();
        }
    );


    std::thread thrProducer(
        [&]()
        {
            using namespace std;

            int nNum = 13;
            unique_lock<mutex> lock( mtxQuit );

            while ( ! m_bQuit )
            {
                while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout )
                {
                    nNum = nNum + 13 / 2;

                    unique_lock<mutex> qLock(mtxQueue);
                    cout << "Produced: " << nNum << "\n";
                    nNumbers.push( nNum );
                }
            }
        }   
    );

    std::thread thrConsumer(
        [&]()
        {
            using namespace std;
            unique_lock<mutex> lock(mtxQuit);

            while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout )
            {
                unique_lock<mutex> qLock(mtxQueue);
                if( nNumbers.size() > 0 )
                {
                    cout << "Consumed: " << nNumbers.front() << "\n";
                    nNumbers.pop();
                }               
            }
        }
    );

    thrQuit.join();
    thrProducer.join();
    thrConsumer.join();

    return 0;
}

1
素晴らしい!投稿いただきありがとうございます。前に述べたように、私の目的はミューテックスの概念を単に説明することだけでした。他のすべてのチュートリアルでは、プロデューサーのコンシューマーや条件変数などの概念が追加されたため、非常に困難になりました。
ナビゲーション

4

関数は、呼び出しスレッドのミューテックスを取得するpthread_mutex_lock()か、ミューテックスを取得できるまでスレッドをブロックします。関連するpthread_mutex_unlock()はミューテックスを解放します。

ミューテックスをキューと考えてください。mutexを獲得しようとするすべてのスレッドは、キューの最後に置かれます。スレッドがミューテックスを解放すると、キュー内の次のスレッドが解放され、実行されます。

クリティカルセクションは、非決定が可能であるコードの領域を指します。多くの場合、これは複数のスレッドが共有変数にアクセスしようとしているためです。クリティカルセクションは、何らかの同期が行われるまで安全ではありません。mutexロックは同期の1つの形式です。


1
次の試行スレッドが正確に入ることが保証されていますか?
Arsen Mkrtchyan、2011

1
@Arsen保証なし。これは参考になる類推です。
chrisaycock

3

mutexで保護されている領域を使用する前に、mutex変数を確認する必要があります。そのため、pthread_mutex_lock()は(実装に応じて)mutex1が解放されるまで待機するか、他の誰かがすでにロックしている場合はロックを取得できなかったことを示す値を返します。

ミューテックスは本当に単純化されたセマフォです。それらについて読んで理解すれば、ミューテックスも理解できます。SOのミューテックスとセマフォに関していくつかの質問があります。バイナリセマフォとミューテックスの違いいつミューテックスを使うべきかいつセマフォを使うべきかなど。最初のリンクのトイレの例は、考えられる限りの良い例です。すべてのコードは、キーが利用可能かどうかを確認し、利用可能であれば予約します。トイレ自体を予約するのではなく、鍵を予約することに注意してください。


1
pthread_mutex_lock他の誰かがロックを保持している場合、戻ることはできません。この場合はブロックされ、それがポイントです。pthread_mutex_trylockロックが保持されている場合に戻る関数です。
R .. GitHub ICE HELPING ICEを停止する

1
ええ、私は最初、これがどのような実装であるかを理解していませんでした。
Makis

3

shortexミューテックスの例を探している人のために:

#include <mutex>

int main() {
    std::mutex m;

    m.lock();
    // do thread-safe stuff
    m.unlock();
}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.