原理CPU提供了原子操作、关中断、锁内存总线,内存屏障等机制;OS基于这几个CPU硬件机制,就能够实现锁;再基于锁,就能够实现各种各样的同步机制(信号量、消息、Barrier等等等等)。
所有的同步操作最基础的理论就是原子操作。内存屏障,锁都是为了保证在不同的平台或者是CPU类型下的原子操作。
原子操作在单核,单线程/无中断,且编译器不优化的情况下是确定的,是按照C/C++代码顺序执行的,所以不存在异步问题。
为了能够理解异步操作产生的原因,我们先来了解下CPU处理的过程:
1.先启动的处理器为有序处理器,指令处理顺序:a.读取指令
b.执行指令如果寄存器可写就从内存取出a的数据到寄存器,寄存器不可写就等待
c.寄存器处理指令
d.将寄存器结果存入内存
2.现在的处理器大多数为乱序处理器,处理顺序:a.读取指令
b.指令被划分到指令队列
c.指令在队列中等待,如果寄存器可写就从内存取出a的数据到寄存器,寄存器不可写就等待
d.寄存器处理指令
e.将执行结果存入队列(而不是立即写入寄存器堆)
f.只有当所有更早的请求执行的指令结果被写入内存之后,执行的结果才会被存入内存(执行结果重排序,让执行看起来是有序的)
那么问题来了:
1.一条简单的a++语句究竟会有这么多条指令,而这一组指令是可以在任意时候异步执行的(共享数据)a.单核多线程情况下,线程是存在中断的,中断的时候cpu调用另一线程的同一指令组,所以是可能出现交叉执行的可能,也就是说单线程或者关掉中断可以解决异步问题,但很多时候这种做法并不实际。
b.多核多线程情况下共享数据被多个核并行处理,不论哪一种处理器都存在同时执行的可能,这就导致了异步问题。
c.现在的编译器都具有优化及自动优化功能,优化之后可能会对共享变脸的访问顺序进行调整,可能会造成与预期不相符的结果。
内存屏障的作用a.在编译时:拒绝编译器优化屏障前后的指令,防止内存乱序访问。
b.在运行时:告诉内存地址总线共享数据地址的数据必须同步(当多个线程同时将一个共享数据地址的数据加载到队列里的时候,先完成处理从cpu到内存的时候总是通知其他线程跟新队列中的该共享数据,从而保证一致性)。
常用场合1.实现同步原语(synchronization primitives)
2.实现无锁数据结构(lock-free data structures)
3.驱动程序
锁
从上面可以看出内存屏障并不是锁,而锁是使用了内存屏障实现的一种用户层的同步处理方式,锁使用的汇编原语有LOCK,UNLOCK等是内存屏障的一种隐式形式,它们都是LOCK操作和UNLOCK操作的变种,所以几乎所有的锁都使用了内存屏障。
锁的种类原子锁:使用了锁总线的方式实现原子操作
自旋锁:while等待,不可抢占的单CPU内核下是无效的,有软中断的情况下,必须使用时本地软中断失效的方法。自旋锁更像是一种用户层控制的while等待处理
读写锁: 读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作
互斥锁:沉睡/休眠等待,所以互斥锁比自旋锁调度耗时。
信号量:用于同一时刻有多个个实例能获取锁,可用于表示同时有多少个client请求允许访问同一个数据块,允许锁个数设置为1的时候就是互斥锁.
读写信号量:对同时拥有的读者数不受限制,只能一个写者,写者发现不需要写的时候降级为读者。
顺序锁:用于能够区分读与写的场合,并且是读操作很多、写操作很少,写操作的优先权大于读操作。
读拷贝锁:RCU(read-copy-update)(RCU也是用于能够区分读与写的场合,并且也是读多写少,但是读操作的优先权大于写操作)
小总结
上面的总结,写的比较乱,只是将自己的想法表达出来,没有构思文章结构,还请包含。