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

5 回复

我猜原因是客户端在访问通道之前打印,而服务器在访问通道之后打印。

在我看来,这个并发设置本身是正确的。

更多关于Golang中无缓冲通道的使用问题解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好 @christophberger

感谢回复。我明白了。有没有什么方法可以验证我的代码是否按照预期运行?换句话说,我能否通过打印语句或其他方式观察到这种同步/阻塞行为(即等待服务器从通道读取)?

daneshvar.amrollahi:

你好 @christophberger

感谢回复。我明白了。有没有什么方法可以验证我的代码是否按我预期的方式运行?换句话说,我能通过打印语句或其他方式看到这种同步/阻塞行为(等待服务器从通道读取)吗?

感谢提供这么棒的信息。

如果你真的想观察动作的确切顺序,我猜你需要将两个操作(打印 + 通道操作)视为一个临界区,并用一个共同的互斥锁来保护它们。这样,只有发送者或接收者中的一方可以进行通道操作并打印其进度。

但是,你为什么要验证通道操作是否阻塞呢?阻塞是设计使然,对于无缓冲通道,在另一方准备好写入或读取之前,读写操作不可能不阻塞。

你的代码实际上展示了无缓冲通道的正确行为。问题在于你观察到的输出顺序是由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来改善输出顺序,但严格来说仍然不能保证完全交替。第二个方案使用确认机制确保了严格的发送-接收交替顺序。

回到顶部