请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

buffered channel的工作模式疑问?

buffered channel有缓冲区,缓存区满了,才会阻塞。

  1. 那这样是不是可以理解「channel的发送」其实也是一种goroutine阻塞的操作?
  2. 不带buffer 的channel因为没有缓冲区,所以channel发送数据时立刻就阻塞?
  3. 带buffer的channel,当缓冲区收满后,才会停止阻塞,然后接受的操作也是一次性把缓冲区的全部读完?
  4. 从内存模型看, 一下代码有两种情况,我有疑惑:

A send on a channel happens before the corresponding receive from that channel completes.

var c = make(chan int, 3)
var a string
func f() {
	a = "hello, world"
	c <- 0
}
func main() {
	go f()
	<-c
	print(a)
}

这里说「发送」happens-before「接收」。是不是就可以理解,可执行的代码上先有「发送」,然后才执行接收?
但是又有一个rule:

The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes.

var work []func()
var limit = make(chan int, 3)
func main() {
	work = []func(){
		func() {
			fmt.Println("Work1")
			time.Sleep(time.Second)
		},
		func() {
			fmt.Println("Work2")
			time.Sleep(time.Second)
		},
		func() {
			fmt.Println("Work3")
			time.Sleep(time.Second)
		},
		func() {
			fmt.Println("Work4")
			time.Sleep(time.Second)
		},
	}
	wg := sync.WaitGroup{}
	for _, w := range work {
		wg.Add(1)
		go func(w func()) {
			limit <- 1
			w()
			<-limit
			wg.Done()
		}(w)
	}
	wg.Wait()
}

这里又说接收「第k次的接收」happens-before「k+缓冲区size的发送」。这里是不是在限定,在第一轮“缓冲区满时”的接收必须happens-before在第二轮发送的开始?

正在回答

2回答

同学理解的很透彻了,尤其是对Go Memory Model的理解。这个问题非常好。

那这样是不是可以理解「channel的发送」其实也是一种goroutine阻塞的操作?unbuffered channel的发送是阻塞操作,发送的时刻立刻阻塞。buffered channel可以等到buffer满了才阻塞。

带buffer的channel,当缓冲区收满后,才会停止阻塞,然后接受的操作也是一次性把缓冲区的全部读完?这句话说反了,而且不严密。

发送方:缓冲区未满,可以不阻塞。注意我说可以,不是必须不阻塞。但实际情况下,通常都是不阻塞的。

接收方:不存在“一次性”这种说法。buffered channel仍然可以做成发一个数据,立刻goroutine切换,然后接收方收一个数据。但是为了性能,通常来说会观察到类似“一次性”收完的这种行为。

我们看你没有贴出来的那个例子:

var c = make(chan int, 1) // If the channel were buffered。所以我这里加一个1,看看结果

var a string

func f() {
  a = "hello, world"
  <-c
}

func main() {
  go f()
  c <- 0
  print(a)
}

他说:

If the channel were buffered (e.g., c = make(chan int, 1)) then the program would not be guaranteed to print "hello, world". (It might print the empty string, crash, or do something else.)

按照你“一次性”收完的理解,这里似乎应该永远不会看到打印出hello world。但他的说法是不保证打印hello world,也是might打印空串,crash,等。不过实际上,我们很难观测到这段程序会打出hello world。


2 回复 有任何疑惑可以回复我~
  • 提问者 拧壶冲 #1
    非常感谢!
    回复 有任何疑惑可以回复我~ 2020-12-16 22:43:09
  • 尼克2018 回复 提问者 拧壶冲 #2
    理解很透彻啊兄弟。
    回复 有任何疑惑可以回复我~ 2021-01-09 19:59:25
ccmouse 2020-11-23 17:59:07

A send on a channel happens before the corresponding receive from that channel completes.

The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes.

你是不是觉得这两条规则冲突了?

第一条规则没说是不是buffered,所以都适用,就是说先有发送,再有接收的意思。

第二条规则,你的说法虽然不严密,但它其实想表达的的确是这个意思。(当然,这句话用人话永远都说不严密)。这里的C应该必须>0,也就是必须buffered,这样两条规则就没有冲突。第一条规则讲的是同一“次”发送必须发生在这一“次”接收之前。第二条规则不严密的说法就是“一轮”接收必须发生在”下一轮“发送之前。

2 回复 有任何疑惑可以回复我~
  • 老师,这么说,我更迷糊了,你这里说 先有发送再有接受,但是在 问题:
    老师好, 这里有一个顺序的问题想请教下,为啥先go worker(),再去发数据给chan?
    这个问题中,你又说必须先 go worker(), 先有接受,才能发送
    感觉很矛盾啊。。。
    回复 有任何疑惑可以回复我~ 2021-07-10 12:18:24
  • 我们说的不是同一件事。这个问题中讨论的是发送和接收两个事件完成的时间顺序。不是代码运行的时间顺序。
    严格的说法应该是,由于worker里面会接收数据,因此发送必须在go worker()这句语句之后。
    我们的确要先go worker(),这样worker中的语句和我们main goroutine中go worker()之后的语句才能同时执行。共同前进到发送和接收语句。
    那么到底发送和接收谁先谁后呢?谁先运行到谁就阻塞,后运行到的人不阻塞,并且对方也会从阻塞状态中恢复继续运行。
    我们这个问题讨论的是内存模型,也就是数据真正的传递时机,是先发送,再接收。发送方会在双方都到达发送/接收语句时,准备好数据,确保将变量结果存储进内存等。
    回复 有任何疑惑可以回复我~ 2021-07-16 15:32:15
问题已解决,确定采纳
还有疑问,暂不采纳
意见反馈 帮助中心 APP下载
官方微信