那个看似忙碌但一无所成的并发任务,可能正陷入活锁的漩涡
什么是活锁?从生活中的例子说起
想象一下,你在一条狭窄的走廊里走向对面而来的人。她移动到一边让你通过,但你也做了同样的动作。所以你转移到另一边,她也是这样做的。如此反复,你们就像在跳一场没有尽头的舞蹈,却始终无法通过。
活锁就是:任务或执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试、失败、再尝试、再失败。在Go语言中,活锁指的是程序一直在运行,但是无法取得进展。
与死锁不同,活锁的实体是在不断地改变状态,所谓的"活",而处于死锁的实体表现为等待。活锁有可能自行解开,死锁则不能。
活锁 vs 死锁:关键区别
为了更好地理解活锁,让我们先弄清楚它和死锁的区别:
死锁就像两个司机在一条单行道上迎面停车,谁也不肯后退,就这样永远僵持下去。而死锁是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
活锁则像两个过于礼貌的人在门口相遇:每个人都坚持让对方先走,你向左我也向左,你向右我也向右,结果还是堵在门口。虽然双方都在不断运动,但谁也无法通过。
在技术层面上,活锁应该是一系列进程在轮询地等待某个不可能为真的条件为真。活锁的时候进程是不会blocked,这会导致耗尽CPU资源。
Go语言中的活锁实例
让我们来看一个具体的Go代码示例,它演示了活锁是如何发生的:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
var flag bool
wg.Add(2)
// goroutine 1
go func() {
// 先获取锁资源
fmt.Println("goroutine 1 获取 mu")
mu.Lock()
defer mu.Unlock()
// 然后等待 flag 变量的值变为 true
fmt.Println("goroutine 1 等待标志")
for !flag {
// 不断循环等待
}
// 最终输出并释放锁资源
fmt.Println("goroutine 1 从等待中释放")
wg.Done()
}()
// goroutine 2
go func() {
// 先获取锁资源
fmt.Println("goroutine 2 获取 mu")
mu.Lock()
defer mu.Unlock()
// 然后等待 flag 变量的值变为 true
fmt.Println("GoRoutine2 等待标志")
for !flag {
// 不断循环等待
}
// 最终输出并释放锁资源
fmt.Println("G

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



