使用sync.Cond在Golang中打印奇偶数变量

huangapple go评论82阅读模式
英文:

Golang Even Odd print variable using sync.Cond

问题

我正在学习关于Golang中的并发性。我正在尝试使用两个不同的线程打印奇偶数。我知道打印奇偶数与并发性相比更多涉及到共享资源的同步/顺序访问,但还是值得一试。

我知道可以使用通道或使用waitgroup来实现,我已经做过了,但仍然想尝试使用sync.cond来实现相同的功能。

在下面的代码中,如果我启用两个函数中的最后一个fmt.Println,那么它会打印出超过10个数字,但如果我将其注释掉并启用第一个fmt.Print函数,那么它就正常工作。这是非常奇怪的输出。
我无法理解为什么会发生这种情况,有人可以告诉我我做错了什么吗?

提前感谢。

package main

import (
	"fmt"
	"sync"
	"time"
)

var done bool

func odd(name *int, c *sync.Cond) {
	//fmt.Println(*name, "ODD ")
	if *name >= 9 {
		return
	}
	c.L.Lock()
	for !done {
		c.Wait()
	}
	*name++
	done = false
	fmt.Println(*name, "ODD")
	c.L.Unlock()
	c.Signal()
}

func even(name *int, c *sync.Cond) {

	//fmt.Println(*name, "EVEN ")
	if *name >= 9 {
		return
	}
	c.L.Lock()
	for done == true {
		c.Wait()
	}
	done = true
	*name++
	fmt.Println(*name, "EVEN ")
	c.L.Unlock()
	c.Signal()
}

func main() {
	done = false

	cond := sync.NewCond(&sync.Mutex{})
	val := 0
	for val < 10 {
		go odd(&val, cond)
		go even(&val, cond)
	}
	time.Sleep(10 * time.Second)
	fmt.Println("val final:=  ", val)
}

启用最后一个打印时的输出

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

启用第一个打印时的输出

0 EVEN
1 ODD
2 ODD
4 ODD
4 EVEN
2 ODD
2 EVEN
6 EVEN
10 ODD
2 ODD
2 EVEN
2 ODD
2 EVEN
10 ODD
2 EVEN
2 ODD
2 EVEN
2 EVEN
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 EVEN
2 ODD
2 ODD
2 ODD
2 EVEN
4 ODD
4 EVEN
4 ODD
4 EVEN
4 ODD
4 EVEN
4 ODD
4 EVEN
6 EVEN
6 ODD
6 EVEN
6 ODD
6 EVEN
8 EVEN
8 ODD
8 EVEN
10 EVEN
8 EVEN
10 EVEN
10 ODD
10 EVEN
2 ODD
10 EVEN
10 ODD
2 EVEN
10 EVEN
10 EVEN
val final:= 10

Go Playground链接:
https://go.dev/play/p/_Kcb_04JiPI

英文:

I am learning more about concurrency in Golang.
I am trying to print even-odd numbers using 2 different threads. I know printing even-odd more about synchronization/sequential access of shared resources than concurrency, still worth trying.

I know it can be done using channel or by using waitgroup and I have already done it but still trying to achieve same thing using sync.cond.

In the below code if I enable the last fmt.Println in both function then it print more than 10 number but if I comment it and enable first fmt.Print function then it works fine. Its very strange output.
I am not able to understand why this is happening can anyone let me know what i am doing wrong.

Thanks in advance.

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

var done bool

func odd(name *int, c *sync.Cond) {
	//fmt.Println(*name, &quot;ODD &quot;)
	if *name &gt;= 9 {
		return
	}
	c.L.Lock()
	for !done {
		c.Wait()
	}
	*name++
	done = false
	fmt.Println(*name, &quot;ODD&quot;)
	c.L.Unlock()
	c.Signal()
}

func even(name *int, c *sync.Cond) {

	//fmt.Println(*name, &quot;EVEN &quot;)
	if *name &gt;= 9 {
		return
	}
	c.L.Lock()
	for done == true {
		c.Wait()
	}
	done = true
	*name++
	fmt.Println(*name, &quot;EVEN &quot;)
	c.L.Unlock()
	c.Signal()
}

func main() {
	done = false

	cond := sync.NewCond(&amp;sync.Mutex{})
	val := 0
	for val &lt; 10 {
		go odd(&amp;val, cond)
		go even(&amp;val, cond)
	}
	time.Sleep(10 * time.Second)
	fmt.Println(&quot;val final:=  &quot;, val)
}

First output when last print enabled

1 EVEN <br/>
2 ODD<br/>
3 EVEN <br/>
4 ODD<br/>
5 EVEN <br/>
6 ODD<br/>
7 EVEN <br/>
8 ODD<br/>
9 EVEN <br/>
10 ODD<br/>
11 EVEN <br/>
12 ODD<br/>
13 EVEN <br/>
14 ODD<br/>
15 EVEN <br/>
16 ODD<br/>
17 EVEN <br/>
18 ODD<br/>
19 EVEN <br/>
20 ODD<br/>
21 EVEN <br/>
22 ODD<br/>
23 EVEN <br/>
24 ODD<br/>
25 EVEN <br/>
26 ODD<br/>
27 EVEN <br/>
28 ODD<br/>
29 EVEN <br/>
30 ODD<br/>
31 EVEN <br/>
32 ODD<br/>
33 EVEN <br/>
34 ODD<br/>
35 EVEN <br/>
36 ODD<br/>
37 EVEN <br/>
38 ODD<br/>
39 EVEN <br/>
40 ODD<br/>
41 EVEN <br/>
42 ODD<br/>
43 EVEN <br/>
44 ODD<br/>
45 EVEN <br/>
46 ODD <br/>
47 EVEN <br/>
48 ODD<br/>
49 EVEN <br/>
50 ODD<br/>
val final:= 51 <br/>

Output when First print enabled

0 EVEN<br/>
1 ODD <br/>
2 ODD <br/>
4 ODD <br/>
4 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
6 EVEN <br/>
10 ODD <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
10 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 EVEN <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 EVEN <br/>
2 ODD <br/>
2 ODD <br/>
2 ODD <br/>
2 EVEN <br/>
4 ODD <br/>
4 EVEN <br/>
4 ODD <br/>
4 EVEN <br/>
4 ODD <br/>
4 EVEN <br/>
4 ODD <br/>
4 EVEN <br/>
6 EVEN <br/>
6 ODD <br/>
6 EVEN <br/>
6 ODD <br/>
6 EVEN <br/>
8 EVEN <br/>
8 ODD <br/>
8 EVEN <br/>
10 EVEN <br/>
8 EVEN <br/>
10 EVEN <br/>
10 ODD <br/>
10 EVEN <br/>
2 ODD <br/>
10 EVEN <br/>
10 ODD <br/>
2 EVEN <br/>
10 EVEN <br/>
10 EVEN <br/>
val final:= 10 <br/>

Link for the Go playground: <br/>
https://go.dev/play/p/_Kcb_04JiPI

答案1

得分: 2

最明显的问题出现在循环中:

for val < 10 {
    go odd(&val, cond)
    go even(&val, cond)
}

我猜你的意图是启动两个工作线程,并等待终止事件(val 达到 10)。

除了数据竞争问题之外,上述代码是一个紧密循环,它会在等待终止事件的同时不断生成多个(不必要的)"odd" 和 "even" 协程,导致结果不可预测。

你可以通过在循环中添加以下代码来查看协程爆炸现象

fmt.Println("goroutine #", runtime.NumGoroutine())

要修复这个问题,你需要为 odd()even() 启动一个单独的协程,并使用一个 "done" 通道或上下文取消来向所有协程发出任务完成的信号。这样就不再需要轮询 val 了,因为在其他协程中更新 val 的同时你在读取它,而没有对 "读取" 端进行任何协调,这会导致数据竞争。

英文:

The most glaring issue is with the loop:

for val &lt; 10 {
    go odd(&amp;val, cond)
    go even(&amp;val, cond)
}

I'm guessing your intent was to start two workers and wait for a termination event (val to reach 10).

Data-races aside, the above is a tight loop which will keep generating multiple (and unnecessary) "odd" and "even" goroutines while it waits for the termination event, creating unpredictable results.

You can see the the goroutine explosion by adding this line to the loop:

fmt.Println(&quot;goroutine #&quot;, runtime.NumGoroutine())

To fix, you need to start a single goroutine for both odd() and even() and use a "done" channel or context cancelation to signal the job is done to all goroutines. This then negates the need to poll val - which is a data-race since you are reading it while it is being updated in other goroutine(s) without any coordination on the "read" end.

huangapple
  • 本文由 发表于 2021年12月27日 02:23:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/70488858.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定