golang系列之-channel
channel-管道,是go语言中一种常见的goroutine的通信方式
当前go版本:1.24
快速上手
示例1. 两个goroutine之间使用channel传递数据
1 | message := make(chan string) |
示例2. 使用select同时监听多个goroutine的响应数据,实际上,业务代码中一般都是跟定时器搭配使用
1 | ch1 := make(chan int) |
channel-管道,是go语言中一种常见的goroutine的通信方式
当前go版本:1.24
示例1. 两个goroutine之间使用channel传递数据
1 | message := make(chan string) |
示例2. 使用select同时监听多个goroutine的响应数据,实际上,业务代码中一般都是跟定时器搭配使用
1 | ch1 := make(chan int) |
从1.24版开始,sync.Map改用HashTrieMap重构,与之前的双map实现不同,HashTrieMap更像是一个B树,简单的示例图如下
1 | // root -> | idx0 | idx1 | ... | idx15 | |
使用哈希函数生成64位的哈希值,从高到低4位为一个idx,最多有16层,每个节点可容纳16个元素,最多可容纳16^16=2^64个元素
当前go版本:1.24
HashTrieMap的开关放在文件
src/internal/buildcfg/exp.go的函数ParseGOEXPERIMENT中
1 | type HashTrieMap[K comparable, V any] struct { |
map不支持并发读写,但我们可以转变下思路,将value改为一个指向结构体entry的指针,结构体内部的字段我们是可以随意修改的,如下,将并发读写map改为并发读map,读写转移到entry
1 | type entry struct { |
上面的方法看起来解决了并发读写的问题,但还不够,当有新的key写入时,还是变回了原来的map并发读写。sync.Map提供了一种思路,使用两个map,read负责已有key的并发读写,dirty负责新key的读写,只有当read找不到key,才去找dirty。
现在还剩最后一个问题,read和dirty如何保证数据一致/同步,我们可以改造entry,使其指向value的指针,如此一来,read和dirty的entry可以指向同一个value,如下,这就是sync.Map的大致思路
1 | type entry struct { |
注意:尽管如此,sync.Map并不完美,以上设计导致我们无法直接计算出哈希表的元素数量,需要遍历进行统计,而且还不一定准确
当前go版本:1.23,1.24版本改为HashTrieMap实现
1 | package main |
上述代码运行结果如下
1 | go run ./main.go |
在使用redis/memcached缓存系统时,可能会遇到以下三个问题
缓存击穿问题中,客户端加锁使穿行化访问是一个值得考虑的解决方法,可以降低服务器(cache/db)的压力。但另一方面,这也会让大量的请求被阻塞,吞吐量下降。实际上,同一时刻的请求可以共享响应数据,这就是singleflight解决的问题
singleflight不是标准库的一部份,但go的internal目录内复制了一份singleflight源码,该源码也是本文在讨论的
当前go版本:1.24
1 | package main |
上述代码运行结果如下
1 | go run ./main.go |
可以看到,G0、G1、G2共享result=2,G3、G4共享result=94
RWMutex-读写锁,该锁可以被任意多个reader持有,或被一个writer持有。通过观察RWMutex的源代码实现,可以将RWMutex看作是FIFO队列,具体看后面的详细描述
当前go版本:1.24
1 | package main |
Mutex(MUTualEx)-互斥锁是一种可以保证每次只有一个goroutine访问贡献资源的方法。这个资源可以是一段程序代码、一个整数、一个map、一个struct、一个channel或其他任何东西。通过观察Mutex的源代码实现,可以将Mutex看作是一个队列(FIFO/LIFO),具体看后面的详细描述
当前go版本:1.24
1 | package main |
上述代码运行结果如下
1 | go run ./main.go |
sync.Cond经常用在多个 goroutine 等待,一个 goroutine 通知(事件发生)的场景。如果是一个通知,一个等待,使用互斥锁或 channel 就能搞定了。底层实现基于信号量semaphore
当前go版本:1.24
以下展示一个sync.Cond的使用案例
1 | package main |
并发情况下,如果需要等待所有的goroutine完成任务,需要使用Waitgroup等待
当前go版本:1.24
先简单列举一个使用案例,了解Waitgroup的使用
1 | package main |
上述代码执行后,系统输出如下,可以看到系统等待5个goroutine完成任务后才退出
1 | go run ./main.go |
sync/atomic标准库包中提供的原子操作。原子操作是无锁的,直接通过CPU指令实现。
当你想要在多个goroutine中无锁访问一个变量时,就可以考虑使用atomic包提供的数据类型实现
当前go版本:1.24
以下是一个使用atomic.Uint64数据类型实现的计数器,它确保了多个goroutine按顺序正确更新数据
1 | package main |
sync.Pool-临时对象池,是golang一个很关键的数据结构,通过复用历史对象,缓解因频繁创建、删除对象而导致的内存分配压力、GC压力,在社区中被广泛使用,有如go-gin、kubernetes等
当前go版本:1.24
下面展示一个简单的使用示例,用于帮助用户快速上手
1 | package main |