无需等待的并发代码

Posted

技术标签:

【中文标题】无需等待的并发代码【英文标题】:Concurrent code without waiting 【发布时间】:2016-07-26 09:52:28 【问题描述】:

我正在考虑某种同步原语,但我不知道这种同步被称为什么,或者这样的东西是否会起作用。

所以有一个变量(布尔值),它基本上表示一个线程是否仍在处理一块内存。一开始,布尔值设置为false,这意味着工作线程不在您的内存块上工作。现在主线程为工作线程提供了一个“待办事项列表”,描述了它应该如何处理该内存块。之后,它将布尔值的状态更改为true,以便工作线程知道它现在被允许执行其工作。主线程现在可以继续自己的工作,并在某些位置检查工作线程是否已完成工作,例如如果布尔值再次设置为false。如果仍然是true,则主线程只是继续自己的工作,不会等待工作线程。如果布尔值为false,则主线程知道工作线程已完成并开始处理内存块。

所以布尔值只是在两个线程之间转移一块内存的所有权。如果一个线程当前没有该内存的所有权,它只会继续自己的工作,并反复检查它现在是否再次拥有所有权。这样一来,没有一个线程相互等待,可以继续自己的工作。

这叫什么?如何实现这种行为?

编辑:基本上它是一个互斥体。但它不是等待互斥锁再次解锁,而是继续/跳过关键代码。

【问题讨论】:

这对于std::future 和/或std::packaged_task 来说可能是一个很好的例子。 在做自己的工作的同时定期检查一些外部事件称为“轮询”。它可以用原子变量来实现。这通常不是一个好主意。 你能解释一下原因吗? 因为经常轮询时会浪费资源,而轮询频率不够时会不必要地等待。更喜欢等待事件而不是检查事件,因为等待没有这些缺点。 【参考方案1】:

编辑:基本上它是一个互斥体。但不是等待互斥锁 再次解锁,它会继续/跳过关键代码。

它仍然是一个互斥体,只是带有“try”方法。

在标准 C++ 中,我们讨论的是 std::mutex::try_lock ,它会尝试锁定互斥体,如果失败则返回 false 并继续

class unlocker
 std::mutex& m_Parent;

public : 

unlocker(std::mutex& parent) : m_Parent(parent)
~unlocker() m_Parent.unlock(); 

;

std::mutex mtx;
if (mtx.try_lock())
   unlocker unlock(mtx); // no, you can't use std::lock_guard/unique_lock here
   //success, mtx is free
 else
  // do something else

在 Native OS 的代码上,根据您所在的操作系统,您具有类似的功能,例如 Unix 上的 pthread_mutex_trylock 和 Windows 上的 TryEnterCriticalSection。不用说标准互斥锁可能确实在幕后使用了这些功能

【讨论】:

这是一种有效的方法吗?我需要问一下——在并发编程中,看似简单的方法大部分时间都是错误的。 是否正确 = 这取决于应用程序。通常,更好的选择是总体上减少争用或首先切换到无锁数据类型。但无论如何,你似乎对性能感到困扰。我认为首先应该证明竞争是他的瓶颈 try_lock 的问题在于它不可靠。 Mutex 可以很好地保护资源免受并发访问,但它在信令方面做得相当糟糕。在 C++ 线程库中,如果您需要信号,您可能需要 future(用于一次性信号)或 condition_variable(用于可重用信号)。 @DavidHaim 您可以将std::unique_lockstd::adopt_lock 标签一起使用。 std::unique_lock<std::mutex> lock(mtx, std::adopt_lock); 一个更好的方法是使用 std::try_to_lock 标签:if (auto l = std::unique_lock<std::mutex>(mtx, std::try_to_lock)) ... 【参考方案2】:

如果主线程用完了你会怎么做?

假设您继续检查并继续阅读true。最终,如果没有工作线程的结果,您将到达主线程无法继续的地步。由于您没有更多工作要做,现在唯一剩下的就是一遍又一遍地检查标志的值,浪费其他线程可以用来做有用工作的 CPU 资源。

一般来说,这不是您想要的。相反,您希望操作系统让您的主线程进入睡眠状态,并且只有在工作线程完成处理后才将其唤醒。现代操作系统附带的各种锁和信号量都以这种方式工作。下面有一些内存中的标志,指示谁拥有锁,但围绕它还有一堆逻辑,确保操作系统不会调度无事可做但等待锁准备好的线程。

话虽如此,在某些情况下这不是您想要的。如果你足够确定你不会遇到一个线程只在锁上旋转的情况,并且你想节省操作系统锁带来的开销,只需检查一个你描述的标志可能 em> 是一个可行的选择。

请注意,尽管像这样的低级内容应该保留用于特殊情况,而不是作为工具箱中的第一个工具。最终得到一个不正确的算法或一个没有你想象的那么高效的实现太容易了。如果您决定走这条路,请准备好做一些认真的工作以使其按预期工作。

【讨论】:

以上是关于无需等待的并发代码的主要内容,如果未能解决你的问题,请参考以下文章

如何等待用户的输入并继续进行erlang并发编程中的进一步代码?

无需用户移动鼠标即可将浏览器光标从“等待”变为“自动”

Springboot-async

Java并发之等待/通知机制

第一节:并发与锁

等待多个并发事件完成的模型