一位同事最近遇到了一个问题,归结为我们认为具有两个线程的C ++应用程序中的以下事件序列:
主题A包含互斥锁。
当线程A持有互斥锁时,线程B会尝试锁定它。由于它被搁置,线程B被暂停。
线程A完成它持有互斥锁的工作,从而释放互斥锁。
此后不久,线程A需要触摸受互斥锁保护的资源,因此它会再次锁定它。
看来线程A再次被赋予互斥锁;线程B仍在等待,即使它首先“询问”锁定。
这个事件序列是否符合C ++ 11的std::mutex
和/或pthreads的语义?老实说,我以前从未考虑过互斥锁的这一方面。
答案 0 :(得分:24)
已知问题。 C ++互斥体是操作系统提供的互斥体之上的薄层,而OS提供的互斥体通常不公平。他们不关心FIFO。
同一枚硬币的另一面是线程通常不会被抢占,直到它们的时间片耗尽。因此,此方案中的线程A可能会继续执行,并因此而立即获得互斥锁。
答案 1 :(得分:3)
您无意中建议线程应同步访问同步原语。正如其名称所示,互斥体是关于互斥的。它们不是为控制流量而设计的。如果要通知另一个线程运行的线程,则需要使用为控制流设计的同步原语,即信号。
答案 2 :(得分:2)
•线程A完成它持有互斥锁的工作 释放互斥锁。 •此后不久,线程A需要触及一个资源 受互斥锁保护,所以再锁定它
在现实世界中,当程序运行时。任何线程库或操作系统都不保证。此后" 此后不久"可能对操作系统和硬件意义重大。如果你说,2分钟,那么线程B肯定会得到它。如果你说200毫秒或更低,A或B都没有承诺。
核心数量,不同处理器/核心/线程单元上的负载,争用,线程切换,内核/用户切换,抢占,优先级,死锁检测方案等。人。会有很大的不同。只要看一下远处的绿色信号就不能保证你会把它变成绿色。
如果您希望线程B必须获取资源,您可以使用IPC机制来指示线程B获取资源。
答案 3 :(得分:2)
std :: mutex的保证启用对共享资源的独占访问。其唯一目的是在多个线程尝试访问共享资源时消除竞争条件。
由于性能原因,互斥锁的实现者可能会选择支持当前线程获取互斥锁(通过另一个线程)。允许当前线程获取互斥锁并在不需要上下文切换的情况下前进进度通常是分析/测量所支持的优选实现选择。
或者,可以构造互斥体以优选用于获取的另一个(阻塞的)线程(可能根据FIFO选择)。这可能需要线程上下文切换(在相同或其他处理器核心上)增加延迟/开销。 注意:FIFO互斥体可以以令人惊讶的方式运行。例如。在FIFO支持中必须考虑线程优先级 - 因此除非所有竞争线程具有相同的优先级,否则获取将不是严格的FIFO。
向互斥锁的定义添加FIFO要求会限制实施者在名义工作负载中提供次优性能。 (见上文)
使用互斥锁保护可调用对象队列(std :: function)将启用顺序执行。多个线程可以获取互斥锁,将可调用对象排入队列并释放互斥锁。可调用对象可以由单个线程(如果不需要同步,则为线程池)执行。
答案 4 :(得分:1)
这里的逻辑非常简单 - 线程不会基于互斥锁被抢占,因为这需要为每个互斥锁操作产生成本,这绝对不是你想要的。获取互斥锁的成本足够高,而不会强制调度程序查找其他线程来运行。
如果要解决此问题,可以始终生成当前线程。您可以使用std :: this_thread :: yield() - http://en.cppreference.com/w/cpp/thread/yield - 并且可能提供线程B来接管互斥锁的机会。但在你这样做之前,请允许我告诉你,这是一种非常脆弱的做事方式,并不能保证。或者,你可以更深入地研究这个问题:
为什么在A发布资源时B线程未启动是一个问题?您的代码不应该依赖于这样的逻辑。
如果您真的需要这种逻辑,请考虑使用替代线程同步对象,例如障碍(boost :: barrier或http://linux.die.net/man/3/pthread_barrier_wait)。
调查一下你是否真的需要从A那里释放互斥锁 - 我发现锁定和快速释放互斥锁的做法多次出现代码异味,它通常会影响性能。看看你是否可以将不可变结构中的数据提取分组,你可以使用它。
雄心勃勃,但尝试在没有互斥体的情况下工作 - 使用无锁结构和更多功能方法,包括使用大量不可变结构。我经常发现,将代码更新为不使用互斥锁可以获得相当大的性能提升(并且从mt的角度来看仍能正常工作)
答案 5 :(得分:1)
您可以使用 fair mutex 来解决您的任务,即保证操作的 FIFO 顺序的互斥锁。不幸的是,C++ 标准库没有公平的互斥锁。
幸运的是,有一些开源实现,例如 yamc(一个只有头文件的库)。
答案 6 :(得分:0)
你如何知道:
当线程A持有互斥锁时,线程B会尝试锁定它。 由于它被搁置,线程B被暂停。
你怎么知道线程B被暂停了。你怎么知道在尝试抓住锁之前它不仅仅完成了代码行,还没有抓住锁:
主题B:
x = 17; // is the thread here?
// or here? ('between' lines of code)
mtx.lock(); // or suspended in here?
// how can you tell?
你无法说出来。至少在理论上不是。
因此,获取锁的顺序对于抽象机器(即语言)来说是不可定义的。