14.3.md 2.8 KB

协程的同步:关闭通道-测试阻塞的通道

通道可以被显示的关闭;尽管它们和文件不同:不必每次都关闭。只有在当需要告诉接收者不会再提供新的值的时候,才需要关闭通道。只有发送者需要关闭通道,接收者永远不会需要。

继续看示例goroutine2.go(示例14.2):我们如何在通道的sendData()完成的时候发送一个信号,getData()又如何检测到通道是否关闭或阻塞?

第一个可以通过函数close(ch)来完成:这个将通道标记为无法通过发送操作<-接受更多的值;给已经关闭的通道发送或者再次关闭都会导致运行时的panic。在创建一个通道后使用defer语句是个不错的办法(类似这种情况):

ch := make(chan float64)
defer close(ch)

第二个问题可以使用逗号,ok操作符:用来检测通道是否被关闭。

如何来检测可以收到没有被阻塞(或者通道没有被关闭)?

v, ok := <-ch   // ok is true if v received value

通常和if语句一起使用:

if v, ok := <-ch; ok {
  process(v)
}

或者在for循环中接收的时候,当关闭或者阻塞的时候使用break:

v, ok := <-ch
if !ok {
  break
}
process(v)

可以通过_ = ch <- v来实现非阻塞发送,因为空标识符获取到了发送给ch的任何东西。在示例程序14.2中使用这些可以改进为版本goroutine3.go,输出相同。

实现非阻塞通道的读取,需要使用select(参见章节14.4

示例 14.9-goroutine3.go

package main

import "fmt"

func main() {
	ch := make(chan string)
	go sendData(ch)
	getData(ch)
}

func sendData(ch chan string) {
	ch <- "Washington"
	ch <- "Tripoli"
	ch <- "London"
	ch <- "Beijing"
	ch <- "Tokio"
	close(ch)
}

func getData(ch chan string) {
	for {
		input, open := <-ch
		if !open {
			break
		}
		fmt.Printf("%s ", input)
	}
}

改变了以下代码:

  • 现在只有sendData()是协程,getData()main()在同一个线程中:

    go sendData(ch)
    getData(ch)
    
  • sendData()函数的最后,关闭了通道:

    func sendData(ch chan string) {
    	ch <- "Washington"
    	ch <- "Tripoli"
    	ch <- "London"
    	ch <- "Beijing"
    	ch <- "Tokio"
    	close(ch)
    }
    
  • 在for循环的getData()中,在每次接收通道的数据之前都使用if !open来检测:

    for {
    		input, open := <-ch
    		if !open {
    			break
    		}
    		fmt.Printf("%s ", input)
    	}
    

    使用for-range语句来读取通道是更好的办法,因为这会自动检测通道是否关闭:

    for input := range ch {
    process(input)
    }
    

    阻塞和生产者-消费者模式:

链接