假如 CPU 只有一个核心,使用 CAS 并发竞争的问题

2021-05-03 11:47:14 +08:00
 zhongpingjing
两个线程互相竞争,A 线程获取锁执行,B 线程通过自旋来获取锁。
cpu 只有一个核心,A 线程占用了 CPU,B 应该不能自旋了吧??是不是只能等 A 执行完毕
6341 次点击
所在节点    Java
72 条回复
GuuJiang
2021-05-04 19:29:29 +08:00
@raysonx 这就是你和其他所有人分歧的出发点,没有人说过去掉锁啊,锁是目的,自旋锁只是其中一种手段,去掉自旋 != 去掉锁,不管怎么说,也改变不了在单核环境中自旋就是被优化掉了这个事实
GuuJiang
2021-05-04 19:55:05 +08:00
更正一下,甚至我自己说的“自旋锁只是其中一种手段”这句话都是不准确的,自旋并不是锁的必要条件,锁也不是靠自旋实现的,有没有自旋,自旋多少次都不影响锁行为的正确性,自旋的存在仅仅是作为“预期能够很快得到锁,自旋的代价小于 wait 的代价”这一前提下的一种优化手段而已,既然是优化,那自然是可以调整其参数甚至去掉的
如果“预期能很快得到锁”这个条件满足,那么自旋带来的收益就是正的,如果条件不满足,那还不如不自旋,而在单核条件下这个条件显然是不可能满足的,那关掉自旋(其实就是把自旋次数改为 0)也是一种很自然的选择
lsylsy2
2021-05-04 20:02:21 +08:00
总有种上面争吵双方其实是友军的感觉……

两个线程互相竞争,A 线程获取锁执行,B 线程通过自旋来获取锁。

Q:cpu 只有一个核心,A 线程占用了 CPU,B 应该不能自旋了吧??
A:可以,只不过如果明知只有一个核心的话,这里不应该用自旋,而是用其他的锁,因为自旋在这里“性能约等于浪费 CPU 的 mutex 锁”;
如果需要兼容各种情况,自旋锁是可以用的,只不过在单核的情况下性能较差而已,是可以工作的。

Q:是不是只能等 A 执行完毕
A:如果你的“执行完毕”指的是用 mutex 锁之类基于上下文切换的锁,是的;如果指的是“A 的线程完全运行结束,退出这个线程”,那么不是的,在单核的系统里操作系统也会在多个线程间切换,所以多线程读写数据的时候依旧会有并发、加锁的问题需要处理。
lsylsy2
2021-05-04 20:04:31 +08:00
我这里预设的 LZ 的情景是:开发了一款使用自旋锁的多线程软件,想探讨单核机器上运行该软件是否有影响
这个情景的答案是:性能较低,但是可以正确执行。
raysonx
2021-05-04 20:39:31 +08:00
@GuuJiang 我觉得你在这里把自旋锁理解为了“先自旋,自旋一定次数后再等待普通锁”这种范式,在你的这种语境下自旋只是一种优化手段。而我的讲的自旋锁是纯自旋锁,最差情况下会无限次自旋那种。
raysonx
2021-05-04 20:41:17 +08:00
这种理解的不同会让你认为去掉自旋也不会影响程序的正确性。而在我的语境下自旋就是锁本身。
WuSiYu
2021-05-04 20:45:48 +08:00
没必要用自旋锁(不会有优势,纯粹浪费 CPU time ),但你非要这么用也是可以工作的
raysonx
2021-05-04 20:47:33 +08:00
我讲的自旋锁是类似这种:

class SpinLock {
std::atomic_flag locked = ATOMIC_FLAG_INIT ;
public:
void lock() {
while (locked.test_and_set(std::memory_order_acquire)) { ; }
}
void unlock() {
locked.clear(std::memory_order_release);
}
};
Akiya
2021-05-04 21:44:10 +08:00
单核开多线程的意义是什么?
vk42
2021-05-04 22:01:18 +08:00
@raysonx 你要不要再看看 lz 提问描述和我的回答?哪里说了不需要锁了?
vk42
2021-05-04 22:06:58 +08:00
@raysonx 另外不要以偏盖全。内核的自旋锁和用户态自旋锁语义非常不同,Linux 内核中严格限制自旋锁只用来保护“真并行“情况下的竞争,普通并发情况下的竞争用 mutex,不能瞎用 spinlock
OSDI
2021-05-04 22:07:18 +08:00
@IndexOutOfBounds 超线程是可以多个线程同时执行的。
zagfai
2021-05-04 22:38:25 +08:00
系统 callback 可以打断进程
zagfai
2021-05-04 22:38:54 +08:00
准确来说说叫中断
shayuvpn0001
2021-05-04 22:48:49 +08:00
@GuuJiang
第一层的人看到的是 HAL 下面的硬件行为,事实上单核在开启 SMT 的情况下,是一套算术逻辑单元对应两套寄存器阵列,在这个层级上,连内核空间和用户空间都不会进行区分的,拿到数据就放进去就执行。
第二层的人注意到了操作系统的存在,在 HAL 以上看这个问题,这个层级上才有 Process, Thread 各种抽象的概念,有了这些才有锁,各种锁更像是操作系统提供的 facility 。
IndexOutOfBounds
2021-05-04 23:06:28 +08:00
@namelosw
@OSDI
#40
sry,没有仔细考究就答了
超线程确实可以并行,不过并行度有限,大概率还是无法并行
至于自旋是否是特殊情况(用到的单元很少和其它线程冲突),不太懂

总的来说我觉得,单核超线程实际并行度有限的话,自旋还是不如直接阻塞
kikione
2021-05-05 10:03:34 +08:00
CAS 适用于 线程并发量比较低的情况。如果多个核心,在某个线程改变了旧值,占用 cpu 一个核心,其他线程也可以在其他核心上通过 while(true),不断尝试。如果只有一个核心,其他核心就没发去并发尝试了。所以 B 肯定不能自旋,这时候还不如用 synchronize 。
zoharSoul
2021-05-05 13:48:42 +08:00
@GuuJiang #36
还有个在 负一层的, #12 楼, 不知道他怎么理解成楼主问能不能用多线程的, 还说能新建怎么会不能用, 真是企业级理解, 笑死我了
4kingRAS
2021-05-06 14:42:11 +08:00
这楼生动地展示了面试为什么要搞那么难的重要性,不难就招进来一群参差不齐的人,你在一层,我在三层,他在负一层,我们都以为自己很牛逼。
killeder
2021-05-06 15:15:49 +08:00
感觉大部分都是 1/4 瓶子醋,只有这个 raysonx 兄弟是明白人

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/774722

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX