互斥量是否保证获取顺序?解锁线程在其他人仍在等待时再次使用它

Do mutexes guarantee ordering of acquisition? Unlocking thread takes it again while others are still waiting

一位同事最近遇到了一个问题,归结为我们认为具有两个线程的 C++ 应用程序中的以下事件序列:

这一系列事件是否符合 C++11 的 std::mutex and/or pthreads 的语义?老实说,我以前从未想过互斥量的这个方面。

是否有任何公平保证来防止其他线程饥饿太久,或者有什么方法可以获得这样的保证?

已知问题。 C++ 互斥锁是 OS 提供的互斥锁之上的薄层,而 OS 提供的互斥锁通常是不公平的。他们不关心 FIFO。

同一枚硬币的另一面是线程通常不会被抢占,直到它们 运行 超出它们的时间片。因此,这种场景下的线程A很可能会继续执行,并因此立即获得互斥量。

•Thread A finishes the work that it was holding the mutex for, thus releasing the mutex. •Very shortly thereafter, thread A needs to touch a resource that is protected by the mutex, so it locks it again

在现实世界中,当程序是运行。任何线程库或 OS 均不提供任何保证。这里“此后不久”可能对OS和硬件意义重大。如果说,2分钟,那么线程B肯定能搞定。如果您说 200 毫秒或更短,则 A 或 B 无法保证得到它。

核心数量、不同 processors/cores/threading 单元上的负载、争用、线程切换、kernel/user 切换、抢占、优先级、死锁检测方案等。阿尔。会有很大的不同。光是从远处看绿色信号,你不能保证你会得到它。

如果想让线程B必须获取资源,可以使用IPC机制让线程B获取资源。

您无意中建议线程应该同步对同步原语的访问。顾名思义,互斥量是关于相互排斥的。它们不是为控制流而设计的。如果你想从另一个线程向 运行 发送信号,你需要使用为控制流设计的同步原语,即信号。

std::mutex 的保证是启用对共享资源的独占访问。它的唯一目的是在多个线程尝试访问共享资源时消除竞争条件。

出于性能原因,互斥锁的实现者可能会选择支持当前线程获取互斥锁(而不是另一个线程)。允许当前线程获取互斥量并在不需要上下文切换的情况下向前推进通常是 profiling/measurements 支持的首选实现选择。

或者,可以构造互斥锁以优先使用另一个(阻塞的)线程进行获取(可能根据 FIFO 选择)。这可能需要线程上下文切换(在相同或其他处理器内核上)增加 latency/overhead。 注意:FIFO 互斥体的行为方式令人惊讶。例如。在 FIFO 支持中必须考虑线程优先级 - 因此除非所有竞争线程都具有相同的优先级,否则获取将不是严格的 FIFO。

向互斥体的定义添加 FIFO 要求会限制实施者在标称工作负载中提供次优性能。 (见上文)

使用互斥锁保护可调用对象队列 (std::function) 将启用顺序执行。多个线程可以获取互斥量,将可调用对象入队,然后释放互斥量。可调用对象可以由单个线程执行(如果不需要同步,则可以由线程池执行)。

这里的逻辑很简单——线程不是基于互斥量来抢占的,因为那样每次互斥量操作都会产生成本,这绝对不是你想要的。获取互斥锁的成本已经足够高了,而无需强制调度程序寻找其他线程 运行.

如果你想解决这个问题,你总是可以让出当前线程。您可以使用 std::this_thread::yield() - http://en.cppreference.com/w/cpp/thread/yield - 可能 为线程 B 提供接管互斥体的机会。但在你这样做之前,请允许我告诉你,这是一种非常脆弱的做事方式,并且不提供任何保证。或者,您可以更深入地调查问题:

  1. 为什么A释放资源时B线程没有启动是个问题?您的代码不应依赖于此类逻辑。

  2. 如果您真的需要这种逻辑,请考虑使用其他线程同步对象,例如屏障(boost::barrier 或 http://linux.die.net/man/3/pthread_barrier_wait)。

  3. 调查一下你是否真的需要在那个时候从 A 释放互斥锁——我发现锁定和释放互斥锁不止一次的做法是一种代码味道,它通常会严重影响表现。看看您是否可以将数据提取分组到您可以使用的不可变结构中。

  4. 雄心勃勃,但尝试在没有互斥锁的情况下工作 - 使用无锁结构和更实用的方法,包括使用大量不可变结构。我经常发现将我的代码更新为不使用互斥锁会带来相当大的性能提升(并且从 mt 的角度来看仍然可以正常工作)

你怎么知道这个:

While thread A is holding the mutex, thread B attempts to lock it. Since it is held, thread B is suspended.

你怎么知道线程B被挂起。你怎么知道不是刚刚抢完锁之前的那行代码,而是还没抢到锁:

线程 B:

x = 17; // is the thread here?
// or here? ('between' lines of code)
mtx.lock();  // or suspended in here?
// how can you tell?

你看不出来。至少理论上不是。

因此获取锁的顺序对于抽象机(即语言)来说是不可定义的。

您可以使用公平互斥锁来解决您的任务,即保证操作的 FIFO 顺序的互斥锁。不幸的是,C++ 标准库没有公平的互斥量。

谢天谢地,有 open-source 实现,例如 yamc(一个 header-only 库)。