我在多线程C++代码中遇到一种情况,我需要使一些非常快速的操作成为原子操作(显示为序列化的),因此可以使用自旋锁,例如:
lock mutex: while (lock.test_and_set(std::memory_order_acquire))
unlock mutex: lock.clear(std::memory_order_release);
但是我认为这很聪明,并且使锁定取决于当前是否由多个线程共享数据结构:
lock mutex: if(lockneeded) while (lock.test_and_set(std::memory_order_acquire))
unlock mutex: if(lockneeded)lock.clear(std::memory_order_release);
最初,数据结构仅由一个线程拥有,但是所有者可以将访问权限授予另一个线程,此时,它必须设置需要锁定的变量(该变量本身必须是原子 bool 变量)。
这会工作吗?
编辑:一些上下文。我有一个安排协程的系统。由一个线程一次运行一个挂起的协程队列,直到它挂起或完成,然后再运行下一个。该系统最初是为单线程设计的,因为规范中的协程是顺序编程结构。上下文切换时间非常快,因为协程将堆栈分配的链表用于堆栈,而不是机器堆栈。因此,上下文切换基本上只是指针交换。
然后,我决定有选择地允许一个以上的线程来处理列表,以便协程成为进程。现在,指针交换必须原子完成。交换非常快,因此自旋锁似乎是保护操作的正确方法。
我有一个测试用例,其中我依次运行一组作业,然后使用额外的帮助程序线程再次执行。我有一个现已解决的问题,事实证明它与计划无关。现在,有4个线程运行该进程的速度比1个快3.5倍。
性能目标很简单:我想抹去Go-lang的痕迹。我的系统兼容C/C++ ABI(不支持Go),它使用正确的模型进行流处理(不支持Go),并且它还具有很强的优越性。
我不知道Go可以快速切换上下文。但是,我的测试用例的当前未调整版本(在此版本中,我们不能忘记作业数达到100K会产生延迟(并确保锁上的争用几乎为零))正在5秒钟内处理2百万个进程,这是上下文切换率每秒约40万个开关。我期望如果我用空作业代替慢作业(不做任何协程),则速度将超过每秒1亿个开关。这正在运行200万个进程。现实世界中的速度会更低,实验正在尝试寻找性能的上限。
最佳答案
不,不幸的是,这不起作用。
说线程A看到lockneeded
为假,并在不获取lock
的情况下进入了临界区,然后在临界区的中间发生上下文切换。线程B请求访问数据结构。数据结构不知道线程A在关键部分,因此授予了线程B访问权限。 lockneeded
设置为true,但是线程A已在其关键部分内。然后线程B获取lock
...您可以轻松地看到这是未定义的行为。
除非您可以保证关键部分的lockneeded
不会更改,否则它将无法正常工作。确保lockneeded
不变的一种方法是使用锁来保护它。因此,您需要为lockneeded
的每次访问添加一个锁,从而首先破坏了变量的用途。
高效的C++自旋锁
自旋锁在概念上是如此简单,但是有很多可用的口味。要考虑的重要因素是性能要求(是否真的需要那么高效?),体系结构,线程库,所需的可伸缩性,预期的争用量(如果争用很少,则可以针对非争用情况进行优化),不对称性使用相同的锁(以防止线程出现饥饿)的关键部分,读与写的比率...您可以看到,如果需要超高效的代码,则需要执行许多性能测试。因此,如果您真的不需要性能,则应该使用已有的自旋锁,然后将时间花在其他地方。
但是我们是计算机科学家,我们喜欢最有效的解决方案,因为我们是问题解决者。要获得高度争议的,高度可伸缩的自旋锁,请查看MCS锁。对于总体上良好的自旋锁,我前段时间进行了一些测试,发现pthreads的自旋锁具有很好的可扩展性。
还有另一种方法可以确保线程A不在关键部分中,而无需线程A编写任何内容。这被称为rcu_synchronize,并且为了简化起见,它将涉及线程B设置lockneeded
并等待足够的时间以确保关键部分中的任何线程都能完成它。
由于锁变量的高速缓存未命中而导致的总线流量,天真自旋锁的伸缩性很差(全局写操作会使其他也在旋转的内核无效)。
您可以做的一个简单优化是“读取时旋转”自旋锁:
lock mutex: while (lock.load(std::memory_order_acquire) || lock.test_and_set(std::memory_order_acquire)) {}
unlock mutex: no change
因此,如果另一个线程拥有该锁,则该线程不会受到TSL的困扰(由于“或”短路),但是当另一个线程释放该锁时,该线程会尝试TSL,这可能成功也可能不会成功。不幸的是,此锁在大规模情况下的性能与幼稚的自旋锁一样差,但在低规模,中等竞争的情况下,与幼稚的自旋锁相比,它有时会为您节省一些时间。
关于c++ - 有效的C++条件自旋锁是否可能?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53586329/