Golang中关于通道的问题探讨
Golang中关于通道的问题探讨 大家好,
希望这不是一个愚蠢的问题。我已经在这个论坛里搜索过,但没有找到完全符合我需求的内容。这篇关于可能存在的竞态条件的帖子似乎很接近。
我一直在golangdocs.com上查看关于通道的内容,学习通过通道发送自定义数据。我做了一个微小的改动,在被调用的函数中添加了一个Printf语句。我会把整个代码粘贴在这里,以便于查看。
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
func SendPerson(ch chan Person, p Person) {
ch <- p
fmt.Printf("SendPerson %v\n", p)
}
func main() {
p := Person{"John", 23}
ch := make(chan Person)
go SendPerson(ch, p)
name := (<-ch).Name
fmt.Println(name)
}
如果我多次运行这段代码,通常只看到“John”。但有时会看到:
SendPerson {John 23}
John
我不太明白这是为什么。起初我以为这是我的错误,因为我应该把Printf语句放在被调用函数中处理通道的部分之前,因为我想看看我接收到了什么(这也是我最初添加Printf的目的)。但后来我又仔细想了想。为什么我有时会看到这个输出?我原本期望要么总是看到,要么永远看不到,而不是有时看似随机地出现。我不确定这是否与通道是缓冲的还是非缓冲的有关。
有人能为我指明正确的方向吗?
非常感谢。
更多关于Golang中关于通道的问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
gorountine runtime is uncertain.
啊哈。我想这已经回答了问题。
更多关于Golang中关于通道的问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
goroutine 的运行时是不确定的。 尝试这样做,它不会阻塞直到有消息被发送到通道
for p:=range ch{
fmt.Println(p.name)
}
SendMessage 函数(及其协程)在能够打印消息之前就结束了。
尝试先打印,然后再将消息发送到通道。
当主函数结束时,它会终止所有其他协程。 这类似于一种竞态条件。
我明白了,是的,当你运行 fmt.Print 等函数时,并不能保证它会被执行,因为 goroutine 的执行顺序是没有保证的。此外,当你的主 goroutine 结束时,其他 goroutine 也会随之结束,这可能导致你的打印信息永远不会出现。
非常感谢您提供的代码。它看起来确实比最初的示例要复杂得多。
我断断续续地使用 Go 语言编程已经有一段时间了,但还没有真正使用过通道。在我第一次接触它们时,就遇到了某种竞态条件,或者至少是意料之外的行为,这让我有点惊讶。
我并没有完全理解你的问题。它打印出“John”是因为你只取了那部分内容:
name := (<-ch).Name
fmt.Println(name)
所以,如果你想要通道中的整个结构体,只需:
perso := <-ch fmt.Println(perso)
但就像我之前说的,我不确定我是否理解了你的问题。请告诉我…
你可以改变向通道发送消息的方法,因为你能保证消息被记录。当然,前提是你的程序在后台运行。如果你只能运行一下就结束,那么你可以阻塞等待消息进入通道,或者使用 sync.WaitGroup。
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
Message chan string
}
func NewPerson(name string, age int) *Person {
return &Person{
Name: name,
Age: age,
Message: make(chan string),
}
}
func (s *Person) Send(ch chan *Person) {
s.Message <- "The message was sent"
ch <- s
}
func (s *Person) ReadMessage() {
go func(){
for m := range s.Message {
fmt.Println(m)
}
}()
}
func main() {
p := NewPerson("John", 23)
p. ReadMessage()
ch := make(chan *Person)
go p.Send(ch)
fmt.Println((<-ch).Name)
}
这是一个关于Golang通道和goroutine调度顺序的典型问题。你的观察是正确的,这与通道的缓冲特性以及goroutine的调度机制有关。
在你的代码中,通道是非缓冲的(make(chan Person)),这意味着发送和接收操作是同步的。当SendPerson执行ch <- p时,它会阻塞直到main函数执行<-ch。然而,这两个goroutine(main和SendPerson)的执行顺序并不确定。
关键点:
fmt.Printf("SendPerson %v\n", p)在ch <- p之后执行fmt.Println(name)在(<-ch).Name之后执行- 这两个打印语句在不同的goroutine中,执行顺序取决于调度器
示例重现:
package main
import (
"fmt"
"time"
)
type Person struct {
Name string
Age int
}
func SendPerson(ch chan Person, p Person) {
ch <- p
fmt.Printf("SendPerson %v\n", p)
}
func main() {
// 多次运行观察不同结果
for i := 0; i < 10; i++ {
p := Person{"John", 23}
ch := make(chan Person)
go SendPerson(ch, p)
name := (<-ch).Name
fmt.Println(name)
// 添加短暂延迟,让调度更明显
time.Sleep(time.Microsecond)
}
}
输出可能类似:
John
SendPerson {John 23}
John
SendPerson {John 23}
SendPerson {John 23}
John
John
SendPerson {John 23}
原因分析:
- 当
main接收数据后,两个goroutine都解除阻塞 SendPerson中的Printf和main中的Println开始竞争执行- 如果
SendPerson先被调度,你会看到SendPerson先打印 - 如果
main先被调度,你会看到John先打印 - Go调度器的不确定性导致了这种随机性
验证方案:
// 使用WaitGroup确保顺序观察
package main
import (
"fmt"
"sync"
)
type Person struct {
Name string
Age int
}
func SendPerson(ch chan Person, p Person, wg *sync.WaitGroup) {
defer wg.Done()
ch <- p
fmt.Printf("SendPerson %v\n", p)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
p := Person{fmt.Sprintf("John%d", i), 23 + i}
ch := make(chan Person)
go SendPerson(ch, p, &wg)
name := (<-ch).Name
fmt.Printf("Received: %s\n", name)
}
wg.Wait()
}
要确保总是先看到"SendPerson"打印,需要在发送前打印:
func SendPerson(ch chan Person, p Person) {
fmt.Printf("SendPerson %v\n", p) // 先打印
ch <- p // 后发送
}
或者使用缓冲通道来解耦发送和接收:
func main() {
p := Person{"John", 23}
ch := make(chan Person, 1) // 缓冲大小为1
go SendPerson(ch, p)
// 发送不会阻塞,SendPerson可以立即继续执行Printf
time.Sleep(time.Millisecond) // 确保SendPerson有机会执行
name := (<-ch).Name
fmt.Println(name)
}
这种不确定性是Go并发模型的设计特性,不是代码错误。

