【发布时间】:2018-12-03 01:29:21
【问题描述】:
我在多线程 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);
最初,数据结构仅由一个线程拥有,但可以所有者将访问权限授予另一个线程,此时它必须设置需要锁的变量(必须是atomic bool 本身)。
这行得通吗?
编辑:一些上下文。我有一个调度协程的系统。一个挂起的协程队列由单个线程一次运行一个,直到它挂起或完成,然后运行下一个。该系统最初是为单线程设计的,因为按照规范,协程是顺序编程结构。上下文切换时间非常快,因为协程使用堆分配的链表作为堆栈,而不是机器堆栈。所以上下文切换基本上只是一个指针交换。
然后我决定允许多个线程处理列表,因此协程成为进程。现在指针交换必须以原子方式完成。交换速度非常快,因此自旋锁似乎是保护操作的正确方法。
我有一个测试用例,我在其中连续运行一组作业,然后使用额外的辅助线程再次执行。我有一个问题,我现在已经解决了,结果与日程安排无关。现在,4 个线程运行该进程的速度大约是 1 个线程的 3.5 倍。
性能目标很简单:我想将 Go-lang 从地球上抹去。我的系统兼容 C/C++ ABI(Go 不兼容),它使用正确的流处理模型(Go 不兼容),而且它也是一种非常优秀的语言。
我不知道 Go 的上下文切换速度有多快。但是我的测试用例的当前未调整版本,我们不能忘记作业计数到 100K 以产生延迟(并确保锁上的争用接近零),在 5 秒内处理 200 万个进程,这是上下文切换速率每秒大约 400K 开关。我希望如果我用空作业(什么都不做协程)替换慢速作业,速度将超过每秒 100 万次开关。那是运行 200 万个进程。现实世界的速度会更低,实验试图找到性能的上限。
【问题讨论】:
-
正确格式化帖子并提供minimal reproducible example,如果您想询问特定场景。在处理多线程时,拥有一个完整的示例尤为重要。
-
鉴于
bool需要是原子的,我不确定它是否会在您添加额外指令时提高性能。有没有在单线程的情况下测量过这3种情况(无锁、锁和锁+布尔)的性能,看看实际的影响?如果不是,也许是过早的优化? -
所以你已经到了正确的做法是让 lockneeded 成为具有访问权限的线程的计数器......所以应该根据这个计数器来实现自旋锁...... = > 这几乎是一个互斥锁!
-
@Phil1970:不,我还没有测量性能,我在问它是否会工作。当前代码使用互斥锁。最新型号 Mac 上的并发版本比单线程版本慢 10 倍。它使用条件锁的等价物。在这种情况下,条件测试的成本与使用互斥锁的成本相比是很小的。我计划用自旋锁替换互斥锁。之后我可以尝试不带条件。该应用程序实际上是一个用户空间调度程序。
-
@Oliv:不,它不是计数,它不是 1 个线程或更多。目前的计划是即使其中一个线程消失也不会恢复。所以它从 false 开始,并且可以在第二个线程开始之前的单个线程中变为 true 一次,这就是它的结束。我想这就够了。至少目前可以,还有更多关键问题需要解决。
标签: c++ multithreading