Mutex示例/教程?[关闭]


176

我是多线程的新手,并试图了解互斥锁的工作原理。做了很多谷歌搜索,我发现了一个不错的教程,但是它仍然对它的工作方式产生了一些疑问,因为我创建了自己的程序,其中锁不起作用。

互斥锁的一种绝对不直观的语法pthread_mutex_lock( &mutex1 );是,当我真正想要锁定的是其他变量时,它看起来像互斥锁已被锁定。这种语法是否意味着锁定互斥锁会锁定代码区域,直到互斥锁解锁为止?那么线程如何知道该区域已锁定?[ 更新:线程知道该区域已被 Memory Fencing 锁定 ]。难道这种现象不应该称为临界区吗?[ 更新:关键部分对象仅在Windows中可用,其中这些对象比互斥对象快,并且仅对实现该对象的线程可见。否则,关键部分仅指由互斥锁保护的代码区域 ]

简而言之,能否请您提供最简单的互斥体示例程序以及有关其工作原理的最简单的解释?我确信这将对其他许多新手有所帮助。



1
我的意思不是冒犯性的,但是您对我的最后一句话是对我们的启示是,我们需要较少的类比,而对于互斥体的工作原理以及我们为什么需要它们需要更好的技术解释。
圣哈辛托

@San:没有冒犯:)我的评论仅是建议新手可以得到对互斥量的最短,最清晰的解释。许多类比可能会使新手感到困惑,因此应单独保存不同的类比。我张贴问题和答案的全部原因是因为作为一个新手,我很难阅读冗长的说明和代码示例。我不想让其他人痛苦。
导航

2
@Cory:如果这个答案可以改善,我很乐意接受您的建议。我很高兴很多其他人发现它很有帮助。如果它没有帮助您,那么也会有其他人的答案,他们指出了其他互斥体教程。为什么这么消极?
2013年

Answers:


278

我很谦虚地向世界各地的新手解释这个概念:(我博客上的颜色编码版本也是如此)

很多人跑到一个孤独的电话亭(他们没有手机)与亲人聊天。第一个抓住展位门把手的人是被允许使用电话的人。只要使用电话,他就必须一直握住门的把手,否则其他人将抓住把手,将其扔出去并与妻子交谈:)没有这样的排队系统。当该人结束通话,走出展位并离开门把手时,将允许下一个握住门把手的人使用电话。

一个线程是:每个人
互斥是:门把手
是:人的手
资源是:手机

任何必须执行一些代码行的线程(不应该同时被其他线程修改)(使用电话与妻子交谈),必须先获得一个互斥锁(抓住展位的门把手) )。只有这样,线程才能运行这些代码行(拨打电话)。

线程执行完该代码后,应释放互斥锁,以便另一个线程可以获取互斥锁(其他人可以访问电话亭)。

[ 当考虑现实世界中的互斥访问时,具有互斥体的概念有点荒谬,但是在编程世界中,我想没有其他方法可以让其他线程“看到”一个线程已经在执行某些代码行。这里有递归互斥的概念,但是这个例子只是为了向您展示基本概念。希望该示例可以使您对该概念有一个清晰的了解。]

使用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

相反的明确使用lockunlock,你可以用括号如下图所示,如果您正在使用范围的锁,因为它提供了优势。但是,范围锁具有轻微的性能开销。


2
@San:说实话;是的,我喜欢您尽力向完整的新手解释细节(带有流程)的事实。但是,(请不要误会我的意思)这篇文章的目的是将这个概念作一个简短的解释(因为其他答案都指向较长的教程)。希望您不介意我是否复制整个答案并将其作为单独的答案发布?这样我就可以回滚和编辑我的答案以指向您的答案。
导航

2
@Tom在这种情况下,您不应该访问该互斥量。应该封装它的操作,以便保护它所保护的所有内容免受此类伪造。如果在使用库的公开API时,确保该库是线程安全的,那么可以放心地添加一个截然不同的互斥体来保护自己的共享项。否则,确实如您所建议的那样添加了新的门把手。
圣哈辛托

2
为了扩展我的观点,您想要做的是在展位周围添加另一个更大的房间。房间还可能包含厕所和淋浴。假设一次只允许1个人进入房间。您必须对房间进行设计,以使该房间应具有带把手的门,该门可以像进入电话亭一样保护进入房间。因此,现在,即使您有多余的互斥锁,也可以在任何项目中重复使用电话亭。另一种选择是公开房间中每个设备的锁定机制并管理房间类中的锁。无论哪种方式,您都不会向同一对象添加新的锁。
圣哈辛托

8
您的C ++ 11线程示例错误。TBB也是这样,线索在范围锁中
乔纳森·威基利

3
我很清楚@Jonathan。你似乎错过了我写的那句话(could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer。至于使用作用域锁定,则取决于开发人员,具体取决于他们正在构建哪种类型的应用程序。此答案旨在解决对互斥锁概念的基本了解,而不是让它涉及所有复杂性,因此,欢迎您提出评论和链接,但超出本教程的范围。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.