2012-08-02 113 views
16

互斥体背后的思想是在任何时候只允许一个线程访问一段内存。如果一个线程锁定互斥锁,则任何其他锁定尝试都将被阻塞,直到第一个锁定解锁。但是,这是如何实施的?为了锁定自己,互斥体必须在某处表明它已被锁定。但是如果第二个互斥体在第一个写入的同时正在读取呢?更糟糕的是,如果他们同时锁定互斥锁呢?互斥体会屈服于它预期的相同问题。互斥体如何工作?

互斥体如何工作?

回答

10

低级原子操作。这些本质上是用硬件实现的互斥锁,除非你只能以原子方式执行很少的操作。

考虑以下等效伪代码:

mutex global_mutex; 
void InterlockedAdd(int& dest, int value) { 
    scoped_lock lock(mutex); 
    dest += value; 
} 
int InterlockedRead(int& src) { 
    scoped_lock lock(mutex); 
    return src; 
} 
void InterlockedWrite(int& dest, int value) { 
    scoped_lock lock(mutex); 
    dest = value; 
} 

这些功能被实现为通过CPU的指令,并且它们保证线程间的稠度以各种程度。确切的语义取决于所讨论的CPU。 x86提供了顺序一致性。这意味着这些操作就像按顺序发布一样。这显然涉及阻碍一点。

你可以准确地推测出原子操作可以用互斥来实现,反之亦然。但通常,原子操作由硬件提供,然后由操作系统在其上实现互斥锁和其他同步原语。这是因为有一些算法不需要完整的互斥锁,并且可以操作所谓的“无锁”,这意味着只需使用原子操作来实现一些线程间的一致性。

9

过去曾经使用过的简单实现是使用CPU级原子“锁定和交换”指令。这是一个特殊的指令,用一个内存位置的某个值原子交换给定的值。

线程可能通过尝试将1值交换到内存位置来获取此类互斥锁。如果该值回到0,那么该线程会认为它具有互斥量并且会继续。否则,如果返回的值是1,那么线程会知道某些线程当前具有互斥体。在那种情况下,它会等到再次尝试。

以上是对简单系统中可能发生的事情的高度简化概述。现在真正的操作系统要复杂得多。

+0

这并不回答这个问题。如果两个线程同时“读取和交换”会怎么样?然后两者都返回0. – user1146657 2016-02-02 20:20:54

+1

@ user1146657:“锁定和交换”操作的要点是它是* atomic *,并且CPU确保在任何给定时间只有一个线程可以执行它。因此,根据定义,两个线程*不能同时进行。 CPU是专门为此目的而设计的。 – 2016-02-02 20:23:34

1

所有你需要的是自动做到这一点。它可以由硬件提供,例如原子比较和交换指令,也可以由操作系统通过系统调用提供。一旦它在OS域中,确保只有一个线程试图锁定互斥锁是相当容易的。

实际上,两种方法都结合在一起。例如,参见Linux的futexes。

2

这里是一个什么样的互斥体需要工作的简要概述,这是我的完整文章How does a mutex work?

  • 的缩写形式存在内存中表示锁定状态的整数,值1或0。
  • 互斥量需要一个原子compare_and_swap函数,该函数可以自动尝试修改该值并报告它是否成功。这允许线程同时检查和修改状态。
  • 操作系统需要提供一个函数来等待互斥锁被锁定的情况。在Linux上,低级功能是futex。这会将线程放入队列中,并监视内存中的整数。
  • 涉及的操作还包括数据防护,以防止在锁定之前在内存中修改内容,并在锁定之后完全可用。