互斥锁示例/教程?[已关闭]
互斥锁示例/教程?[已关闭]
关闭。 此问题正在寻求有关书籍、工具、软件库等的建议。它不符合Stack Overflow指南。目前不接受答案。
我们不允许寻求有关书籍、工具、软件库等的推荐。您可以编辑问题,以便可以通过事实和引文来回答。
改善此问题
我正在尝试理解互斥锁的工作原理。经过很多Google搜索,但仍然存在一些不确定性,因为我创建了自己的程序,锁定没有起作用。
互斥锁的一个绝对不直观的语法是pthread_mutex_lock( &mutex1 );
,其中看起来像是锁定了互斥锁,而我真正想要锁定的是其他变量。这种语法是否意味着锁定互斥锁会锁定代码区域,直到互斥锁被解锁?然后线程如何知道该区域已锁定?[更新:线程知道该区域已锁定,通过内存栅栏。]这种现象不应该被称为临界区吗?[更新:临界区对象仅在Windows中可用,其中该对象比互斥体更快,并且仅对实现该对象的线程可见。否则,临界区仅指由互斥体保护的代码区域]
最简单的互斥锁示例程序是什么,以及它工作逻辑的最简单的解释是什么?
虽然互斥锁可以用于解决其他问题,但它们存在的主要原因是提供互斥性,从而解决所谓的竞态条件问题。当两个(或多个)线程或进程同时尝试访问同一个变量时,我们就有可能遇到竞态条件。考虑以下代码
//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条原子指令的链。在多线程系统中,当多个线程同时调用该函数时,在错误的时间读取或写入线程会出现问题。假设我们有两个线程同时运行,一个紧随另一个调用该函数。还假设我们将i初始化为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
发生的是我们有两个线程同时增加i,我们的函数被调用了两次,但结果与该事实不一致。看起来好像只调用了一次该函数。这是因为原子性在机器级别上被“破坏”,这意味着线程可能在错误的时间中断或共同工作。
我们需要一种机制来解决这个问题。我们需要对上面的指令进行一些排序。一个常见的机制是阻止除一个以外的所有线程。Pthread互斥锁使用了这种机制。
任何必须执行一些代码行以不安全地修改由其他线程同时使用的共享值(使用手机给妻子打电话)的任何线程,都应该首先获得互斥锁上的锁。通过这种方式,任何需要访问共享数据的线程必须通过互斥锁。只有在这个线程执行代码之后,才能执行代码。这段代码称为关键部分。
一旦线程执行了关键部分,就应该释放互斥锁上的锁,以便另一个线程可以获得互斥锁。
当考虑人寻求对真实物理对象具有排他访问权时,拥有互斥锁的概念似乎有些奇怪,但在编程时,我们必须有意识地这样做。并发线程和进程没有我们的社会和文化背景,因此我们必须强制它们以良好的方式共享数据。
那么从技术角度来看,互斥锁是如何工作的呢?它不会遭受我们之前提到的相同的竞态条件吗?pthread_mutex_lock()不比简单的变量增量复杂吗?
技术上讲,我们需要一些硬件支持来帮助我们。硬件设计师会给我们一些机器指令,这些指令可以执行多个操作,但保证是原子的。测试与设置(TAS)指令就是其中一个经典的例子。当我们尝试获取资源的锁时,可以使用TAS检查内存中的值是否为0。如果是,这意味着资源正在使用中,我们就不做任何操作(更确切地说,我们通过某种机制等待)。例如,pthread互斥锁会将我们放入操作系统中的一个特殊队列,并在资源变得可用时通知我们。更简单的系统可能需要我们进行紧密的自旋等待,一遍又一遍地检查条件。如果内存中的值不是0,TAS指令会设置该位置为0之外的其他值,而不需要使用其他指令。这就像将两个汇编指令合并成一个,以提供原子性。因此,一旦开始测试和更改该值(如果更改是适当的),就不能被中断。我们可以在这样的指令基础上构建互斥锁。
注意:某些部分可能与先前的答案相似。我接受了他的邀请进行编辑,但他更喜欢原来的方式,所以我将保留我所拥有的一点他的措辞。
这里是我努力尝试向世界各地的新手解释该概念的地方:(我的博客上还有一个彩色版本)
很多人会冲向一个孤零零的电话间(他们没有手机)去和他们的爱人通话。第一个抓住电话间门把手的人,是被允许使用电话的人。他必须在使用电话的同时一直握住门把手,否则其他人会抓住门把手,把他赶出去并与他的妻子通话:)并没有真正的排队系统。当这个人完成电话后,走出电话间并放开门把手时,抓住门把手的下一个人将被允许使用电话。
线程是:每个人
互斥锁是:门把手
锁是:人的手
资源是:电话
任何一个线程想要执行一些代码行,其他线程不能同时修改它们(使用电话与妻子交谈),必须先在互斥锁上获取一个锁(握住电话间的门把手),只有这样线程才能运行这些代码行(打电话)。
一旦线程执行完这些代码,它就应该释放互斥锁上的锁,这样另一个线程才能在互斥锁上获取锁(其他人可以使用电话亭)。
[在考虑真实世界独占访问时,拥有互斥锁的概念有点荒谬,但在编程世界中,我猜没有其他方法让其他线程'看到'一个线程已经在执行某些代码行。有递归互斥锁等概念,但这个例子只是为了向您展示基本概念。希望这个例子给您提供了清晰的概念图片。]
使用C++11线程:
#include#include #include 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
。但是,范围锁定具有轻微的性能开销。