并发编程的原子性 != 事务ACID的原子性
关于原子性,很多人在多个地方都听说过,大家也都背的很熟悉。
在事务的ACID中,有原子性的概念,在并发编程的原子性、可见性、有序性中也有原子性的概念。
有人认为他们是一样的,甚至很多人在说原子性的时候也都是这么说的:
原子性是指事务是一个不可再分割的工作单元,事务中的操作要么都发生,要么都不发生。
但是,其实,虽然都叫原子性,但是此原子性非彼原子性。
在数据库中的原子性,确实是一个不可拆分的工作单元,要么都执行,要么都不执行。因为事务可以Commit、也可以Rollback。
但是在并发编程中,一个操作是没办法rollback的,并且线程在执行过程中也是有可能失败的,失败了是没办法回滚的,难道就说一个操作没办法保证原子性了么?
所以,在并发编程中,我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。这里的原子性是通过加锁的方式来保证的,其实保证的就是一系列操作,不可以被拆分执行,即执行过程中,需要互斥排他,不能有其他线程进行执行。
举一个例子来说明一下这两个原子性的区别。
Redis中的Lua脚本到底能不能保证原子性?
网上很多文章,有人说能,有人说不能。要我说,都对,也都不对。就是因为因为大家搞混了这两个原子性的区别。
我们都知道,当我们想要在一个事务中执行多个命令的时候,会选择使用Lua脚本。
Redis会将一个要执行的Lua脚本封装成一个单独的事务,而脚本执行器在执行这个事务的过程中,
如果有其他客户端请求的时候,会把它暂存起来,等脚本处理完以后,才会再把被暂存的请求恢复执行。
这样就可以保证整个脚本是作为一个整体执行的,中间不会被其他命令插入,这就是所谓的原子性中的”不可拆分”特性。
但是,如果事务执行过程中命令产生错误,事务是不会回滚的,也不会影响后续命令的执行。
也就是说,Redis保证以原子方式执行Lua脚本,但是不保证脚本中所有操作要么都执行或者都会滚。
那就意味着,Redis中Lua脚本的执行,可以保证并发编程中不可再拆分的这个原子性,但是没有保证数据库ACID中要么都执行要么都会滚的这个原子性。