操作系统实现锁的方式:
1.对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位。
2.多处理器,利用test_and_set,Compare_and_swap指令实现进程互斥.
因此,CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。所以,当多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致的情况是不存在的。
#define LOCKED 1
int TestAndSet(int* lockPtr) {//传入一地址
int oldValue;//声明一变量
oldValue = *lockPtr;//把变量设置为 lockPtr地址所指往的值
*lockPtr = LOCKED;//把原变量设为1
return oldValue;//返回旧值
}
volatile int lock = 0;
void Critical() {
while (TestAndSet(&lock) == 1);
//to do 我们要写的业务代码
lock = 0 //解放我们的锁
}
现在线程1,2调用Critical,因为test_and_set 是指令实现为原子性。
假设如果 线程1先进入, lock=1并反回 0; 然后执行业务代码,然后线程2进入,lock=1,返回1,继续自旋。直到1将锁释放。
Compare_and_swap 则是对test_and_set升级。因为 test_and_set 的 LOCKED 已经定死了。
int compare_and_swap(int* reg, int oldval, int newval)
意思是 oldval和你期望值是否相等,如果相等则设值为newval,并返回老值。
CAS的缺点:
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
1.循环时间长开销很大。
如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
(可以试2次CAS,然后挂起线程解决)
2.只能保证一个共享变量的原子操作。
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
(可以,用一个CAS来获取共享变量的操作权限,从而达到控制多个变量的效果)
3.ABA问题。
如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。
(Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。)