Atomic
对于并发操作而言,原子操作是个非常现实的问题。典型的就是i++的问题。 当两个CPU同时对内存中的i进行读取,然后把加一之后的值放入内存中,可能两次i++的结果,这个i只增加了一次。 如何保证多CPU对同一块内存的操作是原子的。 golang中sync/atomic就是做这个使用的。
具体的原子操作在不同的操作系统中实现是不同的。比如在Intel的CPU架构机器上,主要是使用总线锁的方式实现的。 大致的意思就是当一个CPU需要操作一个内存块的时候,向总线发送一个LOCK信号,所有CPU收到这个信号后就不对这个内存块进行操作了。 等待操作的CPU执行完操作后,发送UNLOCK信号,才结束。 在AMD的CPU架构机器上就是使用MESI一致性协议的方式来保证原子操作。 所以我们在看atomic源码的时候,我们看到它针对不同的操作系统有不同汇编语言文件。
如果我们善用原子操作,它会比锁更为高效。自旋锁
即线程(或进程或任务)试图取得锁失败的时候,选择忙等待而不是阻塞自己。选择忙等待的优点,在于如果该线程(或进程或任务)在获取CPU时间片内拿到了锁,即不用进行上下文切换了(因为阻塞,需要进行上下文切换);选择忙等待的缺点,是如果该线程(或进程或任务)在获取CPU时间片内一直获取不到锁,那么CPU就空转了,有点浪费。所以针对锁占用时间短,可以考虑自旋锁。互斥锁
线程(或进程或任务)在操作数据前,先判断数据锁的状态,若已经锁定,则进行该数据的待操作队列,否认就加锁。一旦数据被加互斥锁了,其他线程(或进程或任务)不能在对数据进行任何操作或是加减锁,只有对数据实施加锁的那个线程(或进程或任务)才可以对数据进行操作或是解锁。-
读写锁
读写锁是针对于读写操作的互斥锁。它与普通的互斥锁最大的不同就是,它可以分别针对读操作和写操作进行锁定和解锁操作。读写锁遵循的访问控制规则与互斥锁有所不同。在读写锁管辖的范围内,它允许任意个读操作的同时进行。但是,在同一时刻,它只允许有一个写操作在进行。并且,在某一个写操作被进行的过程中,读操作的进行也是不被允许的。也就是说,读写锁控制下的多个写操作之间都是互斥的,并且写操作与读操作之间也都是互斥的。但是,多个读操作之间却不存在互斥关系。
换句话说:- 同时只能有一个 goroutine 能够获得写锁定。
- 同时可以有任意多个 gorouinte 获得读锁定。
- 同时只能存在写锁定或读锁定(读和写互斥)。
条件变量
与互斥量不同,条件变量的作用并不是保证在同一时刻仅有一个线程访问某一个共享数据,而是在对应的共享数据的状态发生变化时,通知其他因此而被阻塞的线程。条件变量总是与互斥量组合使用。互斥量为共享数据的访问提供互斥支持,而条件变量可以就共享数据的状态的变化向相关线程发出通知。
有疑问加站长微信联系(非本文作者)