首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >互斥示例/教程?

互斥示例/教程?
EN

Stack Overflow用户
提问于 2011-02-14 14:33:47
回答 8查看 275.2K关注 0票数 192

我是多线程的新手,一直在尝试了解互斥锁是如何工作的。我做了很多谷歌搜索,但仍然对它的工作原理产生了一些疑问,因为我创建了自己的程序,其中的锁定不起作用。

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

简而言之,你能不能用最简单的互斥示例程序和最简单的解释来解释它的工作原理?我相信这会对其他新手有帮助。

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2011-03-01 19:55:21

这是我向世界各地的新手解释这个概念的卑微尝试:(在我的博客上也有一个 )

很多人跑到一个孤零零的电话亭(他们没有手机)和他们所爱的人说话。第一个抓住电话亭的门把手的人就是那个被允许使用电话的人。只要他用电话,他就得一直抓住门把手,否则别人就会抓住把手,把他扔出去和他的妻子说话:)根本没有排队系统。当此人打完电话,走出电话亭并留下门把手时,下一个握住门把手的人将被允许使用电话。

一个线程是:每个人

互斥锁的是:门把手

锁定是: The person's

电话资源为:

任何线程必须执行某些代码行,而这些代码行不应该同时被其他线程修改(使用电话与他的妻子交谈),则必须首先获得互斥锁(抓住隔间的门把手)。只有到那时,线程才能运行这些代码行(进行电话调用)。

一旦线程执行了该代码,它就应该释放互斥锁,这样另一个线程就可以获得互斥锁(其他人可以访问电话亭)。

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

使用C++11线程的:

代码语言:javascript
复制
#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编译和运行

如果您使用的是范围锁for the advantage it provides,则可以使用括号as shown here,而不是显式地使用lockunlock。不过,作用域锁有轻微的性能开销。

票数 315
EN

Stack Overflow用户

发布于 2011-03-01 22:01:25

虽然互斥锁可以用来解决其他问题,但它们存在的主要原因是提供互斥,从而解决所谓的竞争条件。当两个(或更多)线程或进程试图并发访问同一变量时,可能会出现竞争情况。考虑以下代码

代码语言:javascript
复制
//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
  i++;
}

这个函数的内部结构看起来非常简单。这只是一句话。但是,典型的伪汇编语言等效项可能是:

代码语言:javascript
复制
load i from memory into a register
add 1 to i
store i back into memory

因为在i上执行递增操作都需要等效的汇编语言指令,所以我们说递增i是一种非大气层操作。原子操作是可以在硬件上完成的操作,并且保证一旦指令执行已经开始就不会被中断。递增i由3个原子指令链组成。在多个线程调用函数的并发系统中,当线程在错误的时间读取或写入时,就会出现问题。想象一下,我们有两个线程同时运行,其中一个线程紧接着另一个线程调用函数。也就是说,我们将i初始化为0。还假设我们有大量的寄存器,并且两个线程使用完全不同的寄存器,因此不会有冲突。这些事件的实际时间可能是:

代码语言:javascript
复制
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。如果是,这将是我们的信号,资源正在使用中,我们什么也不做(或者更准确地说,我们通过某种机制等待。pthreads互斥锁会将我们放入操作系统中的一个特殊队列中,并在资源可用时通知我们。Dumber系统可能需要我们做一个紧密的旋转循环,一遍又一遍地测试条件)。如果内存中的值不是0,则TAS将位置设置为非0的值,而不使用任何其他指令。这就像是将两条汇编指令组合成1来赋予我们原子性。因此,测试和更改值(如果更改是合适的)一旦开始就不能中断。我们可以在这样的指令之上构建互斥锁。

注意:某些部分可能与较早的答案相似。我接受了他的邀请进行编辑,他更喜欢它原来的方式,所以我保留了我所拥有的,其中融入了他的一些言辞。

票数 47
EN

Stack Overflow用户

发布于 2011-02-14 17:06:44

我所知道的最好的线程教程在这里:

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

我喜欢它是关于API的,而不是关于特定实现的,它提供了一些很好的简单示例来帮助您理解同步。

票数 14
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4989451

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档