Go语言基础之并发 goroutine chan

参考文档:
https://www.liwenzhou.com/posts/Go/14_concurrence/
http://www.5lmh.com/并发编程/channel.html

示例一:

package main

import (
	"fmt"
	"sync"
)
var wg sync.WaitGroup
func main() {
	wg.Add(2)
	go nobufChannel() //不带缓冲区的初始化
	go bufChannel()   //有缓冲区的通道
	wg.Wait()
}

//不带缓冲区的初始化
func nobufChannel() {
	defer wg.Done()
	channel1 := make(chan int)
	go func() {
		x := <-channel1
		fmt.Println("channel里取出的数字:", x)
		close(channel1)
	}()
	channel1 <- 10
	fmt.Println("10存入channel")

}

//有缓冲区的通道
func bufChannel() {
	defer wg.Done()
	channel2 := make(chan int, 1) //指定了只能放一个数
	channel2 <- 10
	fmt.Println("10 发送到通道用bufChannel")
	//channel2 <- 20 //todo 指定了只能放一个数,取出后,才能继续向通道里存入数据
	//fmt.Println("20 发送到通道用bufChannel")
	ch2 := <-channel2
	fmt.Println("取出bufChannel中的数据,", ch2)
}

示例二:

package main
import (
	"fmt"
	"sync"
)
var wg sync.WaitGroup
var once sync.Once
func main() {
	fmt.Println("我是返回值")
	a := make(chan int, 100)
	b := make(chan int, 100)
	wg.Add(3)
	go setValue(a)
	go getValue(a, b)
	go getValue(a, b)
	for ret :=range b{
		fmt.Println(ret) //循环打印b通道中的数据
	}
	wg.Wait()
}
func setValue(a chan int) {
	defer wg.Done()
	//循环的存入通道中
	for i := 1; i <= 100; i++ {
		a <- i
	}
	close(a)
}

func getValue(a, b chan int) {
	defer wg.Done()
	for {
		v,ok:= <-a
		if !ok {
			break
		}
		b  <- v //循环读出a通道中的数据,再的存入b通道中
	}
	once.Do(func(){close(b)})
}

close()

可以通过内置的close()函数关闭channel(如果你的管道不往里存值或者取值的时候一定记得关闭管道)

package main

import "fmt"

/*
   1.对一个关闭的通道再发送值就会导致panic。
   2.对一个关闭的通道进行接收会一直获取值直到通道为空。
   3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
   4.关闭一个已经关闭的通道会导致panic。

*/

func main() {
	a := make(chan int, 2)
	a <- 10
	a <- 20
	close(a)
	//for k := range a {
	//	fmt.Println(k)
	//}

	v, ok := <-a
	fmt.Println(v, ok) //10 true
	v, ok = <-a
	fmt.Println(v, ok) //20 true
	v, ok = <-a
	fmt.Println(v, ok) //0 false
	v, ok = <-a
	fmt.Println(v, ok) //0 false
}

如何优雅的从通道循环取值

当通过通道发送有限的数据时,我们可以通过close函数关闭通道来告知从该通道接收值的goroutine停止等待。当通道被关闭时,往该通道发送值会引发panic,从该通道里接收的值一直都是类型零值。那如何判断一个通道是否被关闭了呢?

我们来看下面这个例子:

package main

import "fmt"

// channel 练习
func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	// 开启goroutine将0~100的数发送到ch1中
	go func() {
		for i := 0; i < 100; i++ {
			ch1 <- i
		}
		close(ch1)
	}()
	// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
	go func() {
		for {
			i, ok := <-ch1 // 通道关闭后再取值ok=false
			if !ok {
				break
			}
			ch2 <- i * i
		}
		close(ch2)
	}()
	// 在主goroutine中从ch2中接收值打印
	for i := range ch2 { // 通道关闭后会退出for range循环
		fmt.Println(i)
	}
}

从上面的例子中我们看到有两种方式在接收值的时候判断通道是否被关闭,我们通常使用的是for range的方式。

实现斐波那契数列

//实现斐波那契数列:1 1 2 3 5 8
package main

import "fmt"

//此时的ch只写,quit只读
func fibonacci(ch chan<- int, quit <-chan bool) {
	x, y := 1, 1
	for {
		//监听channel数据的流动
		select {
		case ch <- x:
			x, y = y, x+y
		case flag := <-quit: //没数据时一直处于阻塞状态
			fmt.Println("flag=", flag)
			return //不能用break,因为只跳出select这一层
		}
	}
}
func main() {
	//创建双向通道,没有缓冲的
	ch := make(chan int)    //数字通信
	quit := make(chan bool) //程序是否结束

	//生产者,从channel读取内容
	//新建协程
	go func() {
		for i := 0; i < 8; i++ {
			num := <-ch //只读,没数据时,处于阻塞状态
			fmt.Println("num=", num)
		}
		//可以停止
		quit <- true //写
	}()

	//生产者,产生数字,写入channel
	fibonacci(ch, quit)
}

通道总结

channel常见的异常总结,如下图:

注意:关闭已经关闭的channel也会引发panic。

posted @ 2020-01-15 21:32  HaimaBlog  阅读(348)  评论(1编辑  收藏  举报