37

互斥锁背后的想法是在任何时候只允许一个线程访问一段内存。如果一个线程锁定了互斥体,任何其他锁定尝试都将阻塞,直到第一个解锁。然而,这是如何实现的呢?要锁定自己,互斥锁必须在某个地方设置一个表示它已锁定的位置。但是,如果第二个互斥锁同时读取第一个互斥锁,该怎么办。更糟糕的是,如果他们同时锁定互斥锁怎么办?互斥体将屈服于它旨在防止的相同问题。

互斥锁是如何真正工作的?

4

4 回答 4

24

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

考虑以下等效伪代码:

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 提供顺序一致性。这意味着操作的行为就像它们是按某种顺序按顺序发出的一样。这显然涉及到一些阻塞。

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

于 2012-08-02T03:24:17.593 回答
19

过去使用的一个简单实现是使用 CPU 级别的原子“锁定和交换”指令。这是一条特殊指令,它以原子方式将给定值与某个内存位置中的值交换。

线程可以通过尝试将 1 值交换到内存位置来获取这样的互斥锁。如果该值返回为 0,则线程将假定它具有互斥锁并继续。否则,如果返回值为 1,则线程将知道某个其他线程当前拥有互斥锁。在这种情况下,它会等到再次尝试。

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

于 2012-08-02T03:24:09.720 回答
14

这是互斥锁需要工作的快速概述,它是我的完整文章互斥锁如何工作的缩写形式?

  • 内存中有一个整数表示锁定状态,值为 1 或 0。
  • 互斥体需要一个原子compare_and_swap函数,它可以原子地尝试修改该值并报告它是否成功。这允许线程同时检查和修改状态。
  • 操作系统需要提供一个函数在互斥锁被锁定的情况下等待。在 Linux 上,低级函数是futex. 这会将线程放入队列中,并监视内存中的整数。
  • 涉及的操作还包括数据围栏,以防止内存中的修改在锁定之前可见,并且在锁定之后完全可用。
于 2017-07-21T03:43:44.337 回答
5

您所需要的就是以原子方式进行。它可以由硬件提供,例如原子比较和交换指令,也可以由操作系统通过系统调用提供。一旦它进入操作系统域,就很容易确保只有一个线程试图锁定互斥锁。

在实践中,这两种方法是结合的。参见例如 Linux 的 futexes。

于 2012-08-02T03:32:48.500 回答