并发编程下利用原子操作实现递增id生成器

作者: veaxen 分类: Golang,并发编程 发布时间: 2019-04-01 16:01

有时候我们在编写并发程序的时候需要产生一个递增的id分配给各不同的执行流,最简单的做法是用锁的方式来实现。但是似乎有更加简洁高效的做法,利用原子操作实现(其利用的是著名的compare and swap思想)。

废话不多说,首先我们来看看代码要怎么写。

Go版本

import "sync/atomic"

//GetIncreaseID 并发环境下生成一个增长的id,按需设置局部变量或者全局变量
func GetIncreaseID(ID *uint64) uint64 {
    var n, v uint64
    for {
        v = atomic.LoadUint64(ID)
        n = v + 1
        if atomic.CompareAndSwapUint64(ID, v, n) {
            break
        }
    }
    return n
}

golang标准库提供了对int32、int64、uint32、uint64、uintptr和unsafe.Pointer6个数据类型的增或减、比较并交换、载入、存储和交换5种操作。具体的这里不再一一赘述。只说明函数中用到的。

函数分析:

本函数接受一个uint64的指针并返回一个uint64类型的数值。函数体中主体部分:在一个for死循环中,先用func LoadUint64(addr *uint64) (val uint64)函数将指针参数ID指向的值“安全的”加载进来。为什么不是用v=*ID直接取值呢?

因为这种方式在大并发环境下同一时间内其他进程/线程对*ID这个值的读写操作是不被限制的,并不安全。在32位架构的系统中甚至会出现只读到一半的情况(数值被写入一半的时候执行了读操作)。用“载入”系列函数(以load开头)进行读原子操作可以避免这种状况。以上即用v = atomic.LoadUint64(ID)而不用v=*ID取值的原因。

CompareAndSwapUint64 函数的作用是比较*IDv,如果相等则*ID = n,比较并赋值是一个原子操作,我们在一个循环中重复进行,直到这个操作成功,这样我们就可以得到了一个新值n,并且这个新值是唯一递增的。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据