互斥示例/教程?
我是新来的multithreading,并试图了解互斥体如何工作。 有很多谷歌search, 我find了一个体面的教程 ,但它仍然留下了一些工作,因为我创build了自己的程序,其中locking不起作用的怀疑。
一个绝对不直观的互斥语法是pthread_mutex_lock( &mutex1 );
,它看起来像互斥体被locking,当我真正想locking的是其他一些variables。 这个语法是否意味着locking一个互斥锁,locking一段代码,直到互斥锁被解锁? 那么线程怎么知道这个区域被locking? [ 更新:线程通过 内存屏蔽 知道该区域被locking ]。 而不是这样的现象应该被称为临界区? [ 更新:临界区对象仅在Windows中可用,其中对象比互斥体快,只有实现它的线程才可见。 否则,关键部分只是指由互斥体保护的代码区域 ]
简而言之,能否请您介绍一下最简单的互斥示例程序以及关于它如何工作的逻辑的最简单可能的解释 ? 我相信这会帮助其他许多新手。
编辑的解释和代码是最受欢迎的(使其更清晰/更短/更简单/更正确)。
在这里,我简单的尝试向世界各地的新手解释这个概念:(我的博客上还有一个彩色编码版本 )
很多人跑到一个单独的电话亭(没有手机)与他们的亲人交谈。 抓住展位门把手的第一个人就是被允许使用手机的人。 只要他使用电话,他必须坚持把手,否则其他人会抓住把手,把他扔出去,跟他的妻子交谈:)没有这样的排队系统。 当该人打完电话,离开展位并离开门把手时,下一个拿到门把手的人将被允许使用该电话。
一个线程是:每个人
互斥体是:门把手
锁是:人的手
资源是:电话
任何线程必须执行一些代码,不能同时由其他线程修改(使用电话与他的妻子交谈),必须首先获得一个互斥锁(抓住该展台的门把手)。 只有这样一个线程才能够运行这些代码行(打电话)。
一旦线程执行了该代码,就应该释放互斥锁,以便另一个线程可以获取互斥锁(其他人可以访问电话亭)的锁。
[ 在考虑真实世界的独占访问时,拥有一个互斥体的概念有点荒谬,但在编程世界中,我想没有别的办法让其他线程'看到'一个线程已经在执行一些代码行了。 有recursion互斥等概念,但这个例子只是为了向你展示基本的概念。 希望这个例子能让你清楚地了解这个概念。 ]
使用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
运行g++ -std=c++0x -pthread -o thread thread.cpp;./thread
使用TBB:您需要使用TBB来运行下面的程序,但是发布TBB代码的目的是通过查看简单的代码来理解locking和解锁的顺序(可以通过不使用acquire和释放 – 这也是exception安全的 – ,但这是更清晰)。
#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
已被弃用。 replace显示在这里 。
此外,如果您使用范围锁来获得它所提供的优势 ,则可以不使用lock
和unlock
来显式使用lock
, 如图所示 。
虽然互斥可以用来解决其他问题,但它们存在的主要原因是提供互斥,从而解决所谓的竞争条件。 当两个(或更多)线程或进程试图同时访问同一个variables时,我们有可能出现竞争状态。 考虑下面的代码
//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是一个非atmoic操作。 primefaces操作是一种可以在硬件上完成的操作,一旦指令执行已经开始,保证不被中断。 递增我由一个3个primefaces指令链组成。 在多个线程正在调用该函数的并发系统中,当线程在错误的时间读取或写入时会出现问题。 想象一下,我们有两个同时运行的线程,一个紧跟在另一个之后调用函数。 我们还要说我们初始化为0.同样假设我们有足够多的寄存器,并且两个线程使用完全不同的寄存器,所以不会有冲突。 这些事件的实际时间可能是:
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
发生的事情是我们有两个线程同时递增,我们的函数被调用了两次,但结果与这个事实不一致。 看起来这个函数只被调用一次。 这是因为primefaces性在机器级别是“破碎的”,这意味着线程可能在相互之间中断或者在错误的时间一起工作。
我们需要一个机制来解决这个问题。 我们需要对上面的说明进行一些sorting。 一个常见的机制是阻止除一个之外的所有线程。 Pthread互斥使用这种机制。
任何线程必须执行一些代码行,这些代码行可能会在其他线程同时不安全地修改共享值(使用电话与他的妻子交谈),首先应该获得对互斥锁的locking。 这样,任何需要访问共享数据的线程都必须通过互斥锁。 只有这样一个线程才能够执行代码。 这部分代码被称为关键部分。
一旦线程执行了临界区,它应该释放对互斥锁的锁,以便另一个线程可以获得对互斥锁的锁。
有一个互斥体的概念似乎有点奇怪,当考虑人类寻求独家访问真实的物理对象,但编程时,我们必须是故意的。 并行的线程和stream程没有我们所做的社会和文化的培养,所以我们必须强迫他们分享数据。
所以从技术上讲,互斥体是如何工作的? 难道它不会受到我们之前提到的相同的竞争条件吗? 是不是pthread_mutex_lock()有点复杂,一个简单的增量variables?
从技术上说,我们需要一些硬件支持来帮助我们。 硬件devise师给我们的机器指令做的不止一件事,但保证是primefaces的。 这样一个指令的典型例子是testing和设置(TAS)。 当试图获取资源上的锁时,我们可能会使用TAS检查内存中的值是否为0.如果是这样,那将是我们的信号,即资源正在使用,而我们什么都不做(或者更准确,我们通过某种机制等待,一个pthreads互斥体会把我们放到操作系统中的一个特殊的队列中,并在资源可用的时候通知我们,Dumber系统可能要求我们做一个严格的自旋循环,一遍又一遍的testing条件) 。 如果内存中的值不为0,则TAS将该位置设置为0以外的值,而不使用任何其他指令。 这就像把两个汇编指令合并为1来给我们primefaces性。 因此,testing和改变数值(如果改变是适当的)一旦开始就不能中断。 我们可以在这样的指令之上构build互斥锁。
注意:有些章节可能与之前的答案类似。 我接受了他的邀请来编辑,他更喜欢原来的方式,所以我保留了我的东西,并注入了一点他的意思。
我知道的最好的线程教程在这里:
https://computing.llnl.gov/tutorials/pthreads/
我喜欢它写的关于API,而不是一个特定的实现,它提供了一些很好的简单的例子来帮助你理解同步。
最近我偶然发现了这个post,认为它需要标准库的c ++ 11互斥体(即std :: mutex)的更新解决scheme。
我已经粘贴了下面的一些代码(我的第一步是一个互斥体 – 我学习了与HANDLE,SetEvent,WaitForMultipleObjects等win32并发)。
由于这是我第一次尝试使用std :: mutex和朋友,我很乐意看到评论,build议和改进!
#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()
或者获取调用线程的互斥量,或者阻塞线程直到获得互斥量。 相关的pthread_mutex_unlock()
释放互斥锁。
把互斥锁想象成一个队列; 每个试图获取互斥量的线程都将放在队列的末尾。 当一个线程释放互斥体时,队列中的下一个线程就会closures,现在正在运行。
关键部分是指非确定性可能的代码区域。 通常这是因为多个线程试图访问共享variables。 关键部分是不安全的,直到某种同步到位。 互斥锁是一种同步forms。
在使用由互斥锁保护的区域之前,您应该检查互斥variables。 所以你的pthread_mutex_lock()可能(取决于实现)等到mutex1被释放,或者返回一个值,表示如果别人已经locking了它,locking将不能被获得。
互斥量实际上只是一个简化的信号量。 如果你阅读了解并了解它们,你就会理解互斥体。 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