Go 并发编程一年回顾 (2021)
鸟窝
大道至简 Simplicity is the ultimate form of sophistication
Go 汇编示例 Go Web 开发示例 Go 数据库开发教程
by smallnest
Go 并发编程一年回顾 (2021)
去年的时候我写了一篇Go 并发编程一年回顾, 如今 2021 年也快结束了,Go 1.18 的特性已经冻结,美国页很快进入了假期模式,趁这个节点,我们回顾一下近一年 Go 并发编程的进展。
TryLock 终于要发布
很久以来 (可以追溯到 2013 年#6123), 就有人提议给 Mutex 增加 TryLock 的方法,被大佬们无情的拒绝了,断断续续,断断续续的一直有人提议需要这个方法,如今到了 2021 年,Go team 大佬们终于松口了,增加了相应的方法 (#45435)。
一句话来说,Mutex 增加了 TryLock, 尝试获取锁, RWMutex 增加了 TryLock 和 TryRLock 方法,尝试获取写锁和读锁。它们都返回 bool 类型。如果返回 true, 代表已经获取到了相应的锁,如果返回 false, 则表示没有获取到相应的锁。
本质上,要实现这些方法并不麻烦,接下来我们看看相应的实现 (去除了 race 代码)。
首先是 Mutex.TryLock:
func (m *Mutex) TryLock() bool {
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
return true
}
return false
}
也就是利用 aromic.CAS 操作 state 字段,如果当前没有被锁或者没有等待锁的情况,就可以成功获取到锁。不会尝试 spin 和与等待者竞争。
不要吐槽上面的代码风格,可能你觉得不应该写成下面的方式吗?原因在于我删除了 race 代码,那些代码块中包含 race 代码,所以不能像下面一样简写:
func (m *Mutex) TryLock() bool {
return atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)
}
读写锁有些麻烦,因为它有读锁和写锁两种情况。
首先看 RWMutex.TryLock(去除了 race 代码):
func (rw *RWMutex) TryLock() bool {
if !rw.w.TryLock() {
return false
}
if !atomic.CompareAndSwapInt32(&rw.readerCount, 0, -rwmutexMaxReaders) {
rw.w.Unlock()
return false
}
return true
}
首先底层的 Mutex.TryLock, 尝试获取 w 字段的锁, 如果成功,需要检查当前的 Reader, 如果没有 reader, 则成功, 如果此时不幸还有 reader 没有释放读锁,那么尝试 Lock 也是不成功的, 返回 false。注意返回之前一定要把 rw.w 的锁释放掉。
接下来看 RWMutex.TryRLock(去除了 race 代码):
func (rw *RWMutex) TryRLock() bool {
for {
c := atomic.LoadInt32(&rw.readerCount)
if c < 0 {
return false
}
if atomic.CompareAndSwapInt32(&rw.readerCount, c, c+1) {
return true
}
}
}
这段代码首先检查 readerCount, 如果为负值,说明有 writer,此时直接返回 false。
如果没有 writer, 则使用 atomic.CAS 把 reader 加 1, 如果成功,返回。如果不成功,那么此时可能有其它 reader 加入,或者也可能有 writer 加入,因为不能判断是 reader 还是 writer 加入,那么就用一个 for 循环再重试。
如果是 writer 加入,那么下一次循环 c 可能就是负数,直接返回 false, 如果刚才是有 reader 加入,那么它再尝试加 1 就好了。
以上就是新增的代码,不是特别复杂。Go team 不情愿的把这几个方法加上了, 同时有很贴心的提示 (恐吓):
Note that while correct uses of TryLock do exist, they are rare,
and use of TryLock is often a sign of a deeper problem
in a particular use of mutexes.
WaitGroup 的字段变化
先前,WaitGroup 类型使用[3]uint32
作为state1
字段的类型,在 64 位和 32 位编译器情况下,这个字段的 byte 的意义是不同的,主要是为了对齐。虽然使用一个字段很 “睿智”, 但是阅读起来却很费劲,现在,Go team 把它改成了两个字段,根据对齐规则,64 位编译器会对齐相应字段,讲真的,我们不差那 4 个字节。
type WaitGroup struct {
noCopy noCopy
// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
// 64-bit atomic operations require 64-bit alignment, but 32-bit
// compilers only guarantee that 64-bit fields are 32-bit aligned.
// For this reason on 32 bit architectures we need to check in state()
// if state1 is aligned or not, and dynamically "swap" the field order if
// needed.
state1 uint64
state2 uint32
}
// state returns pointers to the state and sema fields stored within wg.state*.
func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {
if unsafe.Alignof(wg.state1) == 8 || uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
// state1 is 64-bit aligned: nothing to do.
return &wg.state1, &wg.state2
} else {
// state1 is 32-bit aligned but not 64-bit aligned: this means that
// (&state1)+4 is 64-bit aligned.
state := (*[3]uint32)(unsafe.Pointer(&wg.state1))
return (*uint64)(unsafe.Pointer(&state[1])), &state[0]
}
}
64 位对齐情况下 state1 和 state2 意义很明确,如果不是 64 位对齐,还得巧妙的转换一下。
Pool 中使用 fastrandn 替换 fastrand
Go 运行时中提供了fastrandn
方法,要比fastrand() % n
快很多,相关的文章可以看下面中的注释中的地址。
//go:nosplit
func fastrand() uint32 {
mp := getg().m
// Implement wyrand: https://github.com/wangyi-fudan/wyhash
if goarch.IsAmd64|goarch.IsArm64|goarch.IsPpc64|
goarch.IsPpc64le|goarch.IsMips64|goarch.IsMips64le|
goarch.IsS390x|goarch.IsRiscv64 == 1 {
mp.fastrand += 0xa0761d6478bd642f
hi, lo := math.Mul64(mp.fastrand, mp.fastrand^0xe7037ed1a0b428db)
return uint32(hi ^ lo)
}
// Implement xorshift64+
t := (*[2]uint32)(unsafe.Pointer(&mp.fastrand))
s1, s0 := t[0], t[1]
s1 ^= s1 << 17
s1 = s1 ^ s0 ^ s1>>7 ^ s0>>16
t[0], t[1] = s0, s1
return s0 + s1
}
//go:nosplit
func fastrandn(n uint32) uint32 {
// This is similar to fastrand() % n, but faster.
// See https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
return uint32(uint64(fastrand()) * uint64(n) >> 32)
}
所以 sync.Pool 中使用fastrandn
做了一点点修改,用来提高性能。好卷啊,这一点点性能都来压榨, 关键,这还是开启 race 才会执行的代码。
sync.Value 增加了 Swap 和 CompareAndSwap 两个便利方法
如果使用 sync.Value, 这两个方法的逻辑经常会用到,现在这两个方法已经添加到标准库中了。
func (v *Value) Swap(new interface{}) (old interface{})
func (v *Value) CompareAndSwap(old, new interface{}) (swapped bool)
Go 1.18 中虽然实现了泛型,但是一些库的修改有可能在将来的版本中实现了。在泛型推出来之后,atomic 对类型的支持会有大大的加强,所以将来 Value 这个类型有可能退出历史舞台,很少被使用了。(参考 Russ Cox 的文章Updating the Go Memory Model)
整体来说,Go 的并发相关的库比较稳定,并没有大的变化。
[Older
Go 泛型系列:再简化,省略接口
](/2021/10/24/go-generic-eliding-interface/)
原创图书
分类
- Android12
- C++1
- DOTNET1
- Docker5
- Go178
- Java64
- Linux7
- Rust12
- Scala18
- 分享1
- 前端开发18
- 区块链8
- 大数据60
- 工具28
- 数据库3
- 架构26
- 算法4
- 管理2
- 网络编程13
- 读书笔记2
- 运维2
- 高并发编程20
标签云
AndroidApacheBenchBowerC#CDNCQRSCRCCSSCompletableFutureComsatCuratorDSLDisruptorDockerEmberFastJsonFiberGAEGCGnuplotGoGradleGruntGulpHadoopHazelcastIPFSIgniteJVMJavaKafkaLambdaLinuxLongAdderMathJaxMavenMemcachedMetricsMongoNetty
归档
- November 20211
- October 20213
- August 20212
- July 20213
- June 20214
- May 20213
- April 20212
- March 20212
- February 20211
- January 20212
- December 20203
- November 20202
- September 20201
- August 20201
- July 20202
- June 20202
- May 20204
- April 20201
- March 20203
- February 20202
- January 20205
- December 20196
- November 20192
- October 20196
- September 20197
- August 20197
- July 20197
- June 20191
- May 20192
- April 20193
- March 20191
- February 20196
- January 20195
- December 20182
- November 20184
- October 20182
- September 20186
- August 20185
- July 20183
- June 20183
- May 20182
- April 20181
- March 20186
- February 20184
- January 20183
- December 20177
- November 20174
- October 20176
- September 20174
- August 20174
- July 20174
- June 20177
- May 20174
- April 20177
- March 20176
- February 20173
- January 20173
- December 20165
- November 20167
- October 20166
- September 20165
- August 20164
- July 201612
- June 201614
- May 20166
- April 201614
- March 20167
- February 20168
- January 20161
- December 20153
- November 201510
- October 20159
- September 201512
- August 201512
- July 201512
- June 20158
- May 20157
- April 201515
- March 201510
- February 20154
- January 201512
- December 201428
- November 201412
- October 201410
- September 201428
- August 201419
- July 20141
近期文章
友情链接
© 2021 smallnest
Powered by Hexo
首页 归档 github 网站群 Go 汇编示例 Go Web 开发示例 Go 数据库开发教程 RPCX 官网 RPC 开发指南 Scala 集合技术手册 关于