可能重复:
Are incrementers / decrementers (var++, var--) etc thread safe?
您能否在汇编代码级别为我描述为什么在单个核心计算机上认为从两个不同线程递增值不安全?
答案 0 :(得分:9)
i++
有三个操作:
i
提取到注册表i
在这些操作之间,调度程序可能会中断线程,以便可以运行不同的线程(并修改i
)。
答案 1 :(得分:9)
考虑可能为i++
之类的语句生成的指令。当然这取决于你的架构/指令集,但它可能会有以下几点:
LOAD @i, r0 ;load the value of 'i' into a register from memory
ADD r0, 1 ;increment the value in the register
STORE r0, @i ;write the updated value back to memory
现在考虑如何在操作系统中实现多线程,无论机器有多少核心。在最基本的层面上,操作系统需要一些工具来中断当前线程的执行,保存其状态,并执行上下文切换到不同的线程。操作系统没有自动方式知道用户线程内的哪些指令应被视为原子操作,并且能够在任何两条指令之间启动上下文切换。
那么如果操作系统在LOAD
和ADD
之间执行从一个线程到另一个线程的上下文切换会发生什么?假设i
以值0开始,因此当第一个线程被换出时,r0
将被设置为0。操作系统会将此值保存为该线程状态的一部分。现在第二个线程运行,并执行相同的LOAD
语句。内存中的值仍为0,因此r0
再次加载0。线程递增该值并将其写回内存,将i
的值设置为1.现在第一个线程恢复执行,操作系统将r0
的值恢复为0,作为它的上下文切换。第一个线程现在执行增量,将r0
设置为1,值1再次存储在i
中。现在i
的值不正确,因为已经应用了两个增量,但该值仅增加了1.
简而言之,即使i++
是高级语言中的单个语句,它也会生成多个汇编语言指令,并且这些指令不会被操作系统/运行时视为原子环境,除非你在它们周围添加额外的同步逻辑。
答案 2 :(得分:2)
您的问题是标记汇编程序,但询问i ++。您无法保证C代码中的i++
将编译为更改内存的单个指令。如果你有多个线程用一条指令从内存中加载i
,用另一条指令递增它,然后用第三条线程将它写回内存,第一个和第三条之间的线程切换会导致{{1失去了。
答案 3 :(得分:1)
线程1读取旧值
定时器中断熄灭
内核恢复第二个线程
线程2读取旧值
线程2递增它
线程二写它
计时器熄灭
内核恢复线程1
线程一增量
线程一商店
现在你落后了。
答案 4 :(得分:1)
如果处理器没有可以增加内存位置内容的单个指令,编译器就必须执行以下操作:
load location, registerA
increment registerA
store registerA, location
因此,即使任何单个指令是原子的,序列也不是。即使只有一个
increment location
指令无法保证编译器会使用它。例如,编译器可能已经进行了一些优化,并且使用寄存器来保存一些常用值,只在编译器语言的内存模型中的任何排序规则强制要求的情况下将其存储回内存。
答案 5 :(得分:0)
什么阻止系统在读取值和写入值的时间之间去掉一个线程?当然,它不太可能发生,但在标准操作系统上,内核可以随时获得中断并决定另一个线程应该运行。此时,两个线程都将读取相同的值,并且两个线程将以相同的方式递增。但是,第二个线程可以运行另一个时间片,增加数千次,然后当第一个线程被重新安排时,将通过写入过时值来消除第二个线程的所有前进进度。
答案 6 :(得分:0)
无法预测从单个核心上的2个线程执行的指令序列。以下是两个线程尝试执行i ++时可能的顺序,但效果相当于执行i ++一次:
load i # thread 1
system interrupt
load i # thread 2, now i++ in thread 1 is not complete
increment i # thread 2
store i # thread 2
system interrupt
increment i # thread 1, actually the same value
store i