Go并发原语/并发组件/go并发核心语法 之channel

channel:

虽然 他们可以用来同步内存访问; 但他们最好用于在 goroutine 之间传递信息。
命名: 像河流一样,一个channel充当着信息传送的管道,值可以沿着channel传递,然后在下游读出。由于这个特点,通常使用"stream" 来做
        chan 变量名的后缀。
创建channel 语句:
var dataStream chan interface{}
dataStream = make(chan interface{})

要声明 单向channel
声明  实例化一个只能读取的channel 如下:
var dataStream <-chan interface{}
dataStream := make(<-chan interface{})

声明 实例化 一个只能写入 的channel  如下:
var dataStream chan<-  interface{}
dataStream := make(chan<- interface{})
注意: 通常不会看到单向 channel 实例化, 但是会经常看到它们用作函数参数和 返回类型,这是非常有用的,因为当需要时,Go语言会隐式地将双向
channel 转换为 单向 channel。 这里有一个例子:
var receiveChan <- chan interface{}
var sendChan  chan<- interface{}

//通常做法
receiveChan = dataStream
sendChan = dataStream
func main() {
	stringStream := make(chan string)
	go func() {
		if 0 != 1{
			return
		}

		stringStream <- "Hello channels!"
	}()
	fmt.Println(<- stringStream)
}

//fatal error: all goroutines are asleep - deadlock!
从channel 中读取数据可以有两个返回值;
salutation, ok := <-stringStream
可以使用 range 来遍历 channel;
可以从已关闭的channel 读取数据:
可以从已关闭的channel 读取数据:
func main() {
	intStream := make(chan int)
	close(intStream)
	integer, ok := <-intStream
	integer2, ok2 := <-intStream
	integer3, ok3 := <-intStream
	fmt.Printf("(%v): %v", ok, integer)
	fmt.Printf("(%v): %v", ok2, integer2)
	fmt.Printf("(%v): %v", ok3, integer3)

}

// 结果:
// (false): 0(false): 0(false): 0
为什么 已经close channel 了,还可以继续在这个channel上执行读取操作?
这是为了支持有单个上游写入,多个下游读取。
为什么 已经close channel 了,还可以继续在这个channel上执行读取操作?
这是为了支持有单个上游写入,多个下游读取。
关闭 channel 是一种同时给多个 goroutine 发信号的方法。(sync.cond也能 给多个goroutine 发信号)
由于一个被关闭的channel 可以被无数次读取;
func main() {
	begin := make(chan interface{})
	var wg sync.WaitGroup
	for i:=0 ; i<5; i++{
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			<- begin
			fmt.Printf("%v has begun\n", i)
		}(i)
	}

	fmt.Println("Unblocking goroutines...")
	close(begin)
	wg.Wait()
}


//Unblocking goroutines...
//1 has begun
//3 has begun
//2 has begun
//4 has begun
//0 has begun
buffered channel

var dataStream chan interface{}
dataStream = make(chan interface{}, 4)

创建一个有4个容量的缓冲channel。 这意味着我们可以把4个东西放到channel上,不管它是否被读取;
声明 和实例化  还是分成两行比较好; 通过这种方式缓冲channel 是内存中的FIFO队列,用于并发进程进行通信。
func main() {
	var stdoutBuff bytes.Buffer
	defer stdoutBuff.WriteTo(os.Stdout)

	intStream := make(chan int, 4)
	go func() {
		defer close(intStream)
		defer fmt.Fprintln(&stdoutBuff, "Producer Done.")
		for i := 0; i < 5; i++ {
			time.Sleep(time.Second)
			fmt.Fprintf(&stdoutBuff, "Sending:%d\n", i)
			intStream <- i
		}
	}()

	for integer := range intStream {
		fmt.Fprintf(&stdoutBuff, "Received %v.\n", integer)
	}

}

localhost:go_truck zha$ ./test
Sending:0
Sending:1
Sending:2
Sending:3
Sending:4
Producer Done.
Received 0.
Received 1.
Received 2.
Received 3.
Received 4.
localhost:go_truck zhao$ go build test.go
//加上sleep 的执行结果如下
localhost:go_truck zha$ ./test
Sending:0
Received 0.
Sending:1
Received 1.
Sending:2
Received 2.
Sending:3
Received 3.
Sending:4
Producer Done.
Received 4.
localhost:go_truck zha$ 
// chann 改成无缓冲的 执行如下:
localhost:go_truck zhaozhiliang$ ./test
Sending:0
Sending:1
Received 0.
Received 1.
Sending:2
Sending:3
Received 2.
Received 3.
Sending:4
Producer Done.
Received 4.
localhost:go_truck zhaozhiliang$ 

未初始化的channel 默认值 是nil channel
从nil channel中读取数据 会怎样? 死锁/阻塞 <- dataStream   //fatal error: all goroutines are asleep - deadlock!
往 nil  channel中写入数据 会怎样?死锁/阻塞 dataStream <- struct {}{}   //fatal error: all goroutines are asleep - deadlock!
关闭 nil channel 会怎样?  发送 panic  错误
结论:  确保你所使用的channel都会被初始化
表格: 对不同状态的 channel 操作 的结果; todo 表格3-2
操作channel状态结果
Readnil (声明未初始化)阻塞  (可能会看到 deadlock,但还是要理解为阻塞更好)
打开但非空输出值
打开但空阻塞
关闭的<默认值> ,false
只写编译错误
Writenil(声明未初始化)阻塞 deadlock (可能会看到 deadlock,但还是要理解为阻塞更好)
打开的但填满阻塞
打开的且不满写入值
关闭的panic
只读的编译错误
closenilpanic
打开且非空

关闭channel; 读取成功,知道通道耗尽,

然后读取生产者默认值

打开但空关闭chanel;读到生产者的默认值
关闭的panic
只读编译错误

如何 组织不同类型的channel来构建健壮和稳定的东西?
正确的配置channel  / 分配channel 所有权
把 所有权定义为 实例化, 写入 和关闭 channel 的那个 goroutine。
channel 所有者 对 channel 有一个写访问  chan<-
channel 使用者,对channel 有一个只读   <-chan

拥有channel 的 goroutine 应该具备如下:
1。 实例化 channel
2。 执行写操作,或将所有权传递给另一个goroutine
3。 关闭channel
4。 通过一个只读的channel 将它们暴露出来

作为channel的消费者,我只需要担心两件事:
1。 知道 channel 是何时关闭的
2。 正确的处理阻塞
func main() {
	chanOwner := func() <-chan int {
		resultStream := make(chan int, 5) //5
		go func() {
			//defer close(resultStream) //不加这句话 会报错 fatal error: all goroutines are asleep - deadlock!
			//for i := 0; i <= 5; i++ {
			for i := 0; i <= 2; i++ {
				resultStream <- i
			}
			time.Sleep(2 * time.Second)
		}()
		return resultStream
	}

	resultStream := chanOwner()
	//a := <-resultStream
	//fmt.Printf("Received: %d\n", a)
	//b1, ok1 := <-resultStream
	//fmt.Printf("Received: %d, ok=%v\n", b1, ok1)
	//
	//b2, ok2 := <-resultStream
	//fmt.Printf("Received: %d, ok=%v\n", b2, ok2)
	//
	//b3, ok3 := <-resultStream
	//fmt.Printf("Received: %d, ok=%v\n", b3, ok3)
	//
	//b4, ok4 := <-resultStream
	//fmt.Printf("Received: %d, ok=%v\n", b4, ok4)

	for result := range resultStream {
		fmt.Printf("Received: %d\n", result)
	}

	fmt.Println("Done receiving!")
}

//localhost:go_truck zha$ ./test
//Received: 0
//Received: 1
//Received: 2
//fatal error: all goroutines are asleep - deadlock!
//
//goroutine 1 [chan receive]:
//main.main()
//        /Users/zhao/Documents/work_www7_8/360che_go/go_truck/test.go:36 +0x95
//localhost:go_truck zhao$
//将channel  封装在 chan 所有者函数中: 不会出现 往 nil 或 已关闭的 channel 上发生, 而且关闭总是只发生一次, 这从我们的程序中
//消除了大量的风险。原则是 尽量保持channel所有权的范围很小。
/**
这种方式是不好的,如: 一个channel 作为结构体的成员变量,并且有许多方法,它将很快变得不清楚该channel的行为方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值