1.不是原子操作
理由:
1.i++分为三个阶段:
内存到寄存器
寄存器自增
写回内存
这三个阶段中间都可以被中断分离开.
举个例子,现有A,B两个线程,初始i=2;A线程完成1,2步后被切换到B线程,在B线程中执行完这3步,再切换回来,此时A寄存器中的i=3写回内存,故最后i=3,而不是正常的4;所以不是原子操作。
2.++i呢
在多核的机器上,cpu在读取内存i时也会可能发生同时读取到同一值,这就导致两次自增,实际只增加了一次。
3.如何实现i++和++i的原子性呢?
在多核CPU的复杂内存操作中,处理器提供了总线锁和缓存锁两个机制来保证原子性,可以通过处理器提供的很多LOCK前缀的指令来实现。
总线锁:
多个处理器可能会同时从各自的缓存中读取变量,并分别进行操作,在分别写入内存中,想要保证读改写共享变量的操作是原子的,就要使用总线锁来解决,即使用处理器提供的一个LOCK#信号,当有一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞,此时该处理器可以独占共享内存。
缓存锁:
频繁使用的内存将会缓存在处理器的高速缓存里,内存区域如果被缓存到处理器的缓存行里,并且在Lock操作期间被锁定,那么当他执行锁操作回写到内存时,其他处理器会检查各自缓存行内的内存地址,如果发现自己的缓存行对应的地址被修改了,就会将缓存行置于无效状态,下次访问时,重新从内存中读取数据到缓存行,
并允许它的缓存一致性机制(通常采用嗅探技术来实现,即缓存不仅仅是在内存传输的时候才和总线打交道,而是时刻不停的在窥探总线上发生的数据交换,并跟踪其他缓存在做什么,
所以当一个缓存代表它所属的处理器去读写内存时,其他处理器都会得到通知,从而保证各个缓存保持同步)来保证操作的原子性。此时,处理器不会在总线上输出LOCK#信号。
总线锁会把CPU和内存之间的通信锁住,导致其他处理器不能操作其他内存地址的数据,所以总线锁的开销比较大,在某些场合下,会使用缓存锁来代替总线锁进行优化。
但是,当操作的数据不能被缓存在处理器内部或要操作的数据会跨多个缓存行时,处理器会调用总线锁。对于有些处理器不支持缓存锁,就算锁定的内存区域在处理器的缓存行,这时也会调用总线锁。