Golang中无缓冲通道的使用问题解析
Golang中无缓冲通道的使用问题解析 我正在尝试模拟一个带有代理的非常简单的同步消息传递系统。代理有一个通道,客户端向其中写入消息,服务器则从中读取消息。
代码如下:
package main
import (
"fmt"
"strconv"
)
type Broker struct {
ch chan string
}
var broker Broker
func runClient() {
for i := 1; i <= 6; i++ {
message := strconv.Itoa(i)
fmt.Println("CLIENT: sent " + message + " to channel")
broker.ch <- message
}
close(broker.ch)
}
func runServer() {
for message := range broker.ch {
fmt.Println("SERVER: received " + message + " from channel")
}
}
func main() {
broker.ch = make(chan string)
go runClient()
runServer()
}
由于通道缓冲区的大小为零,我期望代码在 broker.ch <- message 处阻塞,直到该消息被另一个 goroutine(服务器)读取。然而,输出却是这样的:
CLIENT: sent 1 to channel
CLIENT: sent 2 to channel
SERVER: received 1 from channel
SERVER: received 2 from channel
CLIENT: sent 3 to channel
CLIENT: sent 4 to channel
SERVER: received 3 from channel
SERVER: received 4 from channel
CLIENT: sent 5 to channel
CLIENT: sent 6 to channel
SERVER: received 5 from channel
SERVER: received 6 from channel
我期望得到的输出是:
CLIENT: sent 1 to channel
SERVER: received 1 from channel
CLIENT: sent 2 to channel
SERVER: received 2 from channel
CLIENT: sent 3 to channel
SERVER: received 3 from channel
CLIENT: sent 4 to channel
SERVER: received 4 from channel
CLIENT: sent 5 to channel
SERVER: received 5 from channel
CLIENT: sent 6 to channel
SERVER: received 6 from channel
看起来缓冲区的容量是 1 而不是 0。我哪里弄错了?如何修改代码才能得到第二个输出?
更多关于Golang中无缓冲通道的使用问题解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我猜原因是客户端在访问通道之前打印,而服务器在访问通道之后打印。
在我看来,这个并发设置本身是正确的。
更多关于Golang中无缓冲通道的使用问题解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
daneshvar.amrollahi:
感谢回复。我明白了。有没有什么方法可以验证我的代码是否按我预期的方式运行?换句话说,我能通过打印语句或其他方式看到这种同步/阻塞行为(等待服务器从通道读取)吗?
感谢提供这么棒的信息。
你的代码实际上展示了无缓冲通道的正确行为。问题在于你观察到的输出顺序是由goroutine调度和打印操作的时序造成的,而不是通道的缓冲行为。
无缓冲通道确实是同步的:发送操作会阻塞直到有接收方准备好接收。在你的代码中,runClient()和runServer()是并发执行的,但打印语句的执行时机受到goroutine调度的影响。
要获得你期望的严格交替输出,你需要确保每次发送后都等待接收完成。以下是修改后的代码:
package main
import (
"fmt"
"strconv"
"sync"
)
type Broker struct {
ch chan string
wg sync.WaitGroup
}
var broker Broker
func runClient() {
defer broker.wg.Done()
for i := 1; i <= 6; i++ {
message := strconv.Itoa(i)
fmt.Println("CLIENT: sent " + message + " to channel")
broker.ch <- message
}
close(broker.ch)
}
func runServer() {
defer broker.wg.Done()
for message := range broker.ch {
fmt.Println("SERVER: received " + message + " from channel")
}
}
func main() {
broker.ch = make(chan string)
broker.wg.Add(2)
go runServer() // 先启动服务器
go runClient() // 再启动客户端
broker.wg.Wait()
}
或者,如果你想要更严格的同步控制,可以使用两个通道:
package main
import (
"fmt"
"strconv"
)
type Broker struct {
msgChan chan string
ackChan chan struct{}
}
var broker Broker
func runClient() {
for i := 1; i <= 6; i++ {
message := strconv.Itoa(i)
fmt.Println("CLIENT: sent " + message + " to channel")
broker.msgChan <- message
<-broker.ackChan // 等待确认
}
close(broker.msgChan)
}
func runServer() {
for message := range broker.msgChan {
fmt.Println("SERVER: received " + message + " from channel")
broker.ackChan <- struct{}{} // 发送确认
}
}
func main() {
broker.msgChan = make(chan string)
broker.ackChan = make(chan struct{})
go runServer()
runClient()
}
第一个修改方案通过调整goroutine的启动顺序和使用WaitGroup来改善输出顺序,但严格来说仍然不能保证完全交替。第二个方案使用确认机制确保了严格的发送-接收交替顺序。


