活锁

活锁

活锁是正在主动执行并发操作的程序,但这些操作无法向前移动程序的状态。你有没有在走廊走向另一个人?她移动到一边让你通过,但你也是这样做的。所以你转移到另一边,但她也是这样做的。想象这会永远持续下去,这就是活锁。

cadence := sync.NewCond(&sync.Mutex{})
go func() {
	for range time.Tick(1 * time.Millisecond) {

		cadence.Broadcast()
	}
}()

takeStep := func() {
	cadence.L.Lock()
	cadence.Wait()
	cadence.L.Unlock()
}

tryDir := func(dirName string, dir *int32, out *bytes.Buffer) bool { //1
	fmt.Fprintf(out, " %v", dirName)
	atomic.AddInt32(dir, 1) //2
	takeStep()              //3
	if atomic.LoadInt32(dir) == 1 {
		fmt.Fprint(out, ". Success!")
		return true
	}
	takeStep()
	atomic.AddInt32(dir, -1) //4
	return false
}

var left, right int32
tryLeft := func(out *bytes.Buffer) bool { return tryDir("left", &left, out) }
tryRight := func(out *bytes.Buffer) bool { return tryDir("right", &right, out) }
  • tryDir 允许一个人尝试向某个方向移动并返回,无论他们是否成功。每个方向都表示为试图朝这个方向移动的次数。

  • 首先,我们通过将该方向递增 1 来朝着某个方向移动。我们将在第 3 章详细讨论 atomic 包。现在,你只需要知道这个包的操作是原子操作。

  • 每个人必须以相同的速度或节奏移动。takeStep 模拟所有动作之间的恒定节奏。

  • 在这里,这个人意识到他们不能在这个方向上放弃。我们通过将该方向递减 1 来表示这一点。

walk := func(walking *sync.WaitGroup, name string) {
	var out bytes.Buffer
	defer func() { fmt.Println(out.String()) }()
	defer walking.Done()
	fmt.Fprintf(&out, "%v is trying to scoot:", name)
	for i := 0; i < 5; i++ { //1
		if tryLeft(&out) || tryRight(&out) { //2
			return
		}
	}
	fmt.Fprintf(&out, "\n%v tosses her hands up in exasperation!", name)
}

var peopleInHallway sync.WaitGroup //3
peopleInHallway.Add(2)
go walk(&peopleInHallway, "Alice")
go walk(&peopleInHallway, "Barbara")
peopleInHallway.Wait()
  • 我对尝试次数进行了人为限制,以便该程序结束。在一个有活锁的程序中,可能没有这种限制,这就是为什么它是一个现实工作中的问题。

  • 首先,这个人会试图向左走,如果失败了,会尝试向右走。

  • 这个变量为程序提供了等待,直到两个人都能够相互通过或放弃。

程序会产生如下输出:

Alice is trying to scoot: left right left right left right left right left right Alice tosses her hands up in exasperation!
Barbara is trying to scoot: left right left right left right left right left right
Barbara tosses her hands up in exasperation!

你可以看到 Alice 和 Barbara 在最终放弃之前持续交互。这个例子演示了一个非常常见的活锁写入原因:两个或多个并发进程试图在没有协调的情况下防止死锁。如果走廊里的人们一致认为只有一个人会移动,那么就不会有活锁:一个人静止不动,另一个人移动到另一边,他们会继续走路。在我看来,活锁比死锁更难以发现,因为它看起来好像程序正在工作。如果活锁程序在你的机器上运行,并且你查看了 CPU 利用率以确定它是否在执行任何操作,那么你可能会认为它是。

下一页