私はマルチスレッドが初めてで、ミューテックスの仕組みを理解しようとしていました。多くのグーグルと まともなチュートリアルを見つけました を行いましたが、ロックが機能しない独自のプログラムを作成したため、それがどのように機能するのか疑問が残りました。
ミューテックスの絶対に直感的でない構文の1つはpthread_mutex_lock( &mutex1 );
です。これは、ミューテックスがロックされているように見えますが、実際にロックしたいのは他の変数です。この構文は、ミューテックスがロック解除されるまで、ミューテックスをロックするとコードの領域がロックされることを意味しますか?次に、スレッドは、領域がロックされていることをどのように知るのですか? [UPDATE:スレッドは、メモリフェンシング によって、領域がロックされていることを認識します。そして、そのような現象はクリティカルセクションと呼ばれるべきではありませんか? [UPDATE:クリティカルセクションオブジェクトはWindowsでのみ使用可能です。このオブジェクトはミューテックスよりも高速で、それを実装するスレッドにのみ表示されます。それ以外の場合、クリティカルセクションは、mutexによって保護されたコードの領域を参照するだけです]
要するに、可能な限り単純なミューテックスのサンプルプログラムと可能な限り単純な説明仕組みのロジックについては?これは他の初心者のplentyに役立つと確信しています。
ここに世界中の初心者に概念を説明する私の謙虚な試みがあります:(a 色分けされたバージョン 私のブログでも)
多くの人が、携帯電話を持たない孤独な電話ブースに走って、愛する人と話をします。ブースのドアハンドルを最初にキャッチしたのは、電話の使用を許可された人です。電話を使用している限り、ドアのハンドルを握り続ける必要があります。そうしないと、誰かがハンドルを握り、彼を捨てて妻と話します:)人が電話を終え、ブースを出てドアハンドルを離れると、ドアハンドルを握る次の人が電話を使用できるようになります。
Athreadis:各人
mutexは:ドアハンドル
lockは:人の手
Theresourceis:電話
同時に他のスレッドによって変更されるべきではないコード行を実行する必要があるスレッド(電話を使用して妻と話す)は、まずmutexのロックを取得する必要があります(ブースのドアハンドルを握る) )。そうしてはじめて、スレッドはこれらのコード行を実行できます(電話をかける)。
スレッドがそのコードを実行したら、ミューテックスのロックを解除して、別のスレッドがミューテックス(電話ブースにアクセスできる他の人)のロックを取得できるようにします。
[実世界の排他的アクセスを検討する場合、ミューテックスを持つという概念は少しばかげていますが、プログラミングの世界では、他のスレッドにスレッドが「見える」ようにする他の方法はなかったと思いますすでに数行のコードを実行しています。再帰ミューテックスなどの概念がありますが、この例は基本的な概念を示すことのみを目的としています。この例がコンセプトの明確な図を提供してくれることを願っています。]
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
を使用してコンパイルおよび実行する
lock
およびunlock
を明示的に使用する代わりに、角カッコ ここに示すように を使用できます。スコープロックを使用している場合 それが提供する利点のため 。ただし、スコープロックにはわずかなパフォーマンスオーバーヘッドがあります。
TBBの場合:以下のプログラムを実行するには TBB が必要ですが、TBBコードを投稿する意図は単純なコードを見るだけでロックとロック解除のシーケンスを理解できます(取得とリリースを使用しないことでスコープ付きロックを示したかもしれません- これも例外に対して安全です -しかし、これはより明確です)。
#include <iostream>
#include "/tbb/mutex.h"
#include "/tbb/tbb_thread.h"
using namespace tbb;
typedef mutex myMutex;
static myMutex sm;
int i = 0;
void someFunction()
{
//Note: Since a scoped lock is used below, you should know that you
//can specify a scope for the mutex using curly brackets, instead of
//using lock.acquire() and lock.release(). The lock will automatically
//get released when program control goes beyond the scope.
myMutex::scoped_lock lock;//create a lock
lock.acquire(sm);//Method acquire waits until it can acquire a lock on the mutex
//***only one thread can access the lines from here...***
++i;//incrementing i is safe (only one thread can execute the code in this scope) because the mutex locked above protects all lines of code until the lock release.
sleep(1);//simply creating a delay to show that no other thread can increment i until release() is executed
std::cout<<"In someFunction "<<i<<"\n";
//***...to here***
lock.release();//releases the lock (duh!)
}
int main()
{
tbb_thread my_thread1(someFunction);//create a thread which executes 'someFunction'
tbb_thread my_thread2(someFunction);
tbb_thread my_thread3(someFunction);
my_thread1.join();//This command causes the main thread (which is the 'calling-thread' in this case) to wait until thread1 completes its task.
my_thread2.join();
my_thread3.join();
}
tbb_thread.h
は廃止されることに注意してください。置換は here と表示されます。
ミューテックスは他の問題を解決するために使用できますが、それらが存在する主な理由は、相互排除を提供し、それによって競合状態として知られるものを解決することです。 2つ(またはそれ以上)のスレッドまたはプロセスが同じ変数に同時にアクセスしようとすると、競合状態になる可能性があります。次のコードを検討してください
//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
i++;
}
この関数の内部はとてもシンプルに見えます。それはただ一つの声明です。ただし、典型的な擬似アセンブリ言語の同等物は次のとおりです。
load i from memory into a register
add 1 to i
store i back into memory
Iでインクリメント操作を実行するには、同等のアセンブリ言語命令がすべて必要なので、iのインクリメントは非アトミック操作であると言います。アトミック操作とは、命令の実行が開始されると中断されないことを保証して、ハードウェア上で完了することができる操作です。インクリメントiは、3つのアトミック命令のチェーンで構成されています。複数のスレッドが関数を呼び出している並行システムでは、スレッドが間違った時間に読み取りまたは書き込みを行うと問題が発生します。同時に2つのスレッドが実行され、1つのスレッドが次のスレッドの直後に関数を呼び出すとします。また、iを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)です。リソースのロックを取得しようとするとき、メモリ内の値が0であるかどうかを確認するためにTASを使用する場合があります。 pthreadsミューテックスは、オペレーティングシステムの特別なキューに入れ、リソースが利用可能になったときに通知します。 。メモリ内の値が0でない場合、TASは他の命令を使用せずにロケーションを0以外に設定します。これは、2つのAssembly命令を1に結合して原子性を与えるようなものです。したがって、値のテストと変更(変更が適切な場合)は、いったん開始されると中断できません。このような命令の上にミューテックスを構築できます。
注:一部のセクションは、以前の回答と同様に表示される場合があります。私は彼の編集への招待を受け入れました。彼は元の方法を好んだので、彼の言葉遣いを少し加えたものを保持しています。
私が知っている最高のスレッドのチュートリアルはこちらです:
https://computing.llnl.gov/tutorials/pthreads/
特定の実装についてではなく、APIについて書かれているのが気に入っています。同期を理解するのに役立つ簡単な例がいくつかあります。
最近この投稿に出くわし、標準ライブラリのc ++ 11ミューテックス(つまりstd :: mutex)の更新されたソリューションが必要だと思います。
以下にいくつかのコードを貼り付けました(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;
}
関数pthread_mutex_lock()
は、acquires呼び出しスレッドのmutexであるか、mutexを取得できるまでスレッドをブロックします。関連するpthread_mutex_unlock()
はミューテックスを解放します。
ミューテックスをキューと考えてください。ミューテックスを取得しようとするすべてのスレッドは、キューの最後に配置されます。スレッドがミューテックスを解放すると、キュー内の次のスレッドが外れ、現在実行中です。
クリティカルセクションは、非決定性が可能なコードの領域を指します。多くの場合、これは複数のスレッドがシェア変数にアクセスしようとしているためです。クリティカルセクションは、何らかの同期が行われるまで安全ではありません。相互排他ロックは、同期の1つの形式です。
ミューテックスによって保護された領域を使用する前に、ミューテックス変数をチェックすることになっています。そのため、pthread_mutex_lock()は(実装に応じて)mutex1が解放されるまで待機するか、他の誰かがすでにロックしている場合はロックを取得できないことを示す値を返すことができます。
ミューテックスは実際には単純化されたセマフォです。それらについて読んで理解すれば、ミューテックスを理解できます。 SOには、ミューテックスとセマフォに関するいくつかの質問があります。 バイナリセマフォとミューテックスの違い 、 ミューテックスをいつ使用し、セマフォをいつ使用するか など。最初のリンクのトイレの例は、考えられるほど良い例です。すべてのコードは、キーが使用可能かどうかを確認し、使用可能な場合は予約します。あなたは本当にトイレ自体を予約するのではなく、鍵を予約することに注意してください。
SEMAPHOREの例::
sem_t m;
sem_init(&m, 0, 0); // initialize semaphore to 0
sem_wait(&m);
// critical section here
sem_post(&m);
リファレンス: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
Shortexミューテックスの例をお探しの場合:
#include <mutex>
using namespace std;
int main() {
mutex m;
m.lock();
// do thread-safe stuff
m.unlock();
return 0;
}