This page looks best with JavaScript enabled

一个有趣问题: 如何让 3 个 goroutine 按顺序循环打印出 ABC?

 ·  ☕ 2 min read

刚刚我在网上冲浪,发现了这个一个问题,于是我想了一下,给了以下几种方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
func fun1() {
	bagA := make(chan struct{})
	bagB := make(chan struct{})
	bagC := make(chan struct{})
	ctx, cancel := context.WithCancel(context.Background())

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			<-bagA
			println("A")
			bagB <- struct{}{}
		}
	}()
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			<-bagB
			println("B")
			bagC <- struct{}{}
		}
	}()
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			<-bagC
			println("C")
			bagA <- struct{}{}
		}
	}()

	go func() {
		bagA <- struct{}{}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop
	cancel()
}

这段代码还可以简化成这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
func fun2() {

	bagA := make(chan struct{})
	bagB := make(chan struct{})
	bagC := make(chan struct{})

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			bagB <- <-bagA
			println("A")
		}
	}()
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			bagC <- <-bagB
			println("B")
		}
	}()
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			bagA <- <-bagC
			println("C")
		}
	}()

	go func() {
		bagA <- struct{}{}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop
	cancel()

}

还可以再简化一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func fun2a() {

	bagA := make(chan struct{})
	bagB := make(chan struct{})
	bagC := make(chan struct{})

	ctx, cancel := context.WithCancel(context.Background())
	run := func(to, from chan struct{}, v string) {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			to <- <-from
            println(v)
		}
	}
	go run(bagB, bagA, "A")
	go run(bagC, bagB, "B")
	go run(bagA, bagC, "C")

	go func() {
		bagA <- struct{}{}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop
	cancel()
}

还有没有别的办法呢?我想了一下,还可以利用 sync.Cond

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
func func3() {
	mu := sync.Mutex{}
	cond := sync.NewCond(&mu)
	value := "A"

	ctx, cancel := context.WithCancel(context.Background())

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			cond.L.Lock()
			for value != "A" {
				cond.Wait()
			}
			println("A")
			value = "B"
			cond.L.Unlock()
			cond.Broadcast()
		}
	}()

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			cond.L.Lock()
			for value != "B" {
				cond.Wait()
			}
			println("B")
			value = "C"
			cond.L.Unlock()
			cond.Broadcast()
		}
	}()

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			cond.L.Lock()
			for value != "C" {
				cond.Wait()
			}
			println("C")
			value = "A"
			cond.L.Unlock()
			cond.Broadcast()
		}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop
    cancel()
}

还可以更精简一些:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
func func3() {
	mu := sync.Mutex{}
	cond := sync.NewCond(&mu)
	value := 'A'
	ctx, cancel := context.WithCancel(context.Background())

	run := func(id rune) {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			cond.L.Lock()
			for value != 'A'+id {
				cond.Wait()
			}
			println(string(value))
			value = 'A' + (id+1)%3
			cond.L.Unlock()
			cond.Broadcast()
		}
	}
	go run(0)
	go run(1)
	go run(2)

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop

	cancel()
}

想了想,这样写没问题吗, 会有 goroutine 泄露吗?
嗯,不会
因为在 cancel()之后, 肯定会有一个改变了 value 的 g0 最先退出,然后 value 再变一次,g1 退出,g2 接下来都也会退出

Share on

EXEC
WRITTEN BY
EXEC
Eval EXEC