golang 学习(三十)管道(channel)介绍以及应用

管道(channel)介绍以及应用

  1. 管道是 Golang 在语言级别上提供的 goroutine 间的通讯方式,我们可以使用 channel 在 多个 goroutine之间传递消息。如果说 goroutine 是 Go 程序并发的执行体,channel 就是它们 之间的连接。channel 是可以让一个goroutine 发送特定值到另一个 gorutine 的通信机制
  2. Golang 的并发模型是 CSP(Comunicating Sequential Processes),提倡通过通信共享内 存而不是通过共享内存而实现通信。
  3. Go 语言中的管道(channel)是一种特殊的类型。管道像一个传送带或者队列,总是遵 循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个管道都是一个具体类 型的导管,也就是声明 chanel 的时候需要为其指定元素类型

channel 类型

channel 是一种类型,一种引用类型
var 变量 chan 元素类型

var ch1 chan int // 声明一个传递整型的管道
var ch2 chan bol // 声明一个传递布尔型的管道
var ch3 chan []int // 声明一个传递 int 切片的管道

创建 channel

声明的管道后需要使用 make 函数初始化之后才能使用
make(chan 元素类型, 容量)

var ch = make(chan int, 4) //创建一个能存储 4 个 int 类型数据的管道

chanel 操作

管道有发送(send)、接收(recive)和关闭(close)三种操作
发送和接收都使用<-符号

func TestChannel(t *testing.T) {
	//channel 管道 是引用数据类型 先入先出
	var ch = make(chan int, 4) //创建管道
	ch <- 10                   //把 10 发送到 ch 中
	a := <-ch                  //获取管道的数据
	fmt.Println(a) //10
	//打印管道的长度 容量
	fmt.Printf("值 %v  长度 %v 容量 %v \n", ch, len(ch), cap(ch)) //值 0xc000014200  长度 0 容量 4
	ch <- 20
	<-ch 
	} 

关闭管道 close(ch)
关于关闭管道需要注意的事情是,只有在通知接收方 goroutine 所有的数据都发送完毕的时
候才需要关闭管道。管道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束
操作之后关闭文件是必须要做的,但关闭管道不是必须的。
关闭后的管道有以下特点:

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

管道阻塞

无缓冲的管道:
如果创建管道的时候没有指定容量,那么我们可以叫这个管道为无缓冲的管道
无缓冲的管道又称为阻塞的管道

func TestChannel() {
//无缓冲的管道
ch := make(chan int)
ch <- 10
fmt.Println("发送成功")
}
//编译报错 fatl ero: al gorutines are asleep - deadlock!
//有缓冲的管道
ch := make(chan int,1)
ch <- 10
a1:=<-ch
fmt.Println(a1)//10
a2:=<-ch
//管道的阻塞  在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock

只要管道的容量大于零,那么该管道就是有缓冲的管道,管道的容量表示管道中能存放元素
的数量。就像你小区的快递柜只有那么个多格子,格子满了就装不下了,就阻塞了,等到别
人取走一个快递员就能往里面放一个。

for range,for i 从管道循环取值

var ch = make(chan int, 4)
for i := 0; i < 4; i++ {
		ch <- i
	}
close(ch)//for range 循环需要执行关闭管道,否则会报deallock
//循环遍历管道的值,注意管道没有key
for v := range ch {
		fmt.Println(v)
	}	
for i := 0; i < 4; i++ {
		fmt.Println(<-ch)
	}	

channel管道和gorouting协程结合应用

//二个协程 二个管道 边写数据 边读数据
import (
	"fmt"
	"sync"
	"testing"
)

func ReadDate(ch chan int)  {
	for v := range ch {
		fmt.Printf("读取管道数据%v\n",v)
	}
	wg.Done()
}
func WriteData(ch chan int)  {
	for i := 0; i < 10; i++ {
		ch<-i
		fmt.Printf("写入管道数据%v\n",i)
	}
	close(ch)
	wg.Done()
}
var wg sync.WaitGroup
func TestChannel(t *testing.T) {
	for v := range ch {
		fmt.Println(v)
	}
	var ch1 = make(chan int,10)
	wg.Add(1)
	go WriteData(ch1)
	wg.Add(1)
	go ReadDate(ch1)
	wg.Wait()
}

求10000内的素数

import (
	"fmt"
	"sync"
	"testing"
)
//前10000数据存入管道
func putNum(ch chan int) {
	for i := 2; i <= 10000; i++ {
		ch <- i
	}
	close(ch)
	wg.Done()
}
//生成素数
func DoNum(ch chan int, num chan int, status chan bool) {
	for v := range ch {
		var flag = true
		for i := 2; i < v; i++ {
			if v%i == 0 {
				flag = false
				break
			}
		}
		if flag {
			num <- v //num素数
		}
	}
	status <- true //记录管道执行状态
	wg.Done()
}
//打印素数
func PrintNum(ch chan int) {
	for v := range ch {
		fmt.Println(v)
	}
	wg.Done()
}

var wg sync.WaitGroup

func TestChannel(t *testing.T) {

	var intChan = make(chan int, 1000)
	var primeChan = make(chan int, 1000)
	var exitChan = make(chan bool, 16)
	wg.Add(1)
	go putNum(intChan)
	for i := 0; i < 16; i++ {//开启16 个协程处理素数判断
		wg.Add(1)
		go DoNum(intChan, primeChan, exitChan)
	}
	wg.Add(1)
	//判断primeChan管道执行完成后关闭
	go func() {
		for i := 0; i < 16; i++ {
			<-exitChan
		}
		close(primeChan)
		wg.Done()
	}()

	wg.Add(1)
	go PrintNum(primeChan)
	wg.Wait()

}

单向管道

有的时候我们会将管道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中 使用管道都会对其进行限制,比如限制管道在函数中只能发送或只能接收

//定义双向管道
var myChan  = make(chan int,2)
//定义只读的channel
var readChan  = make(<-chan int,2)
<-readChan
<-readChan
//定义只写的channel
var writeChan = make(chan<- int,2)
writeChan<-10
writeChan<-20

select 多路复用

  1. 传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock,在实际开发中,可能我们不好确定什么关闭该管道
  2. Go 内置了 select 关键字,可以同时响应多个管道的操作
  3. 使用 select 语句能提高代码的可读性。
    • 可处理一个或多个 channel 的发送/接收操作。
    • 如果多个 case 同时满足,select 会随机选择一个。
    • 对于没有 case 的 select{}会一直等待,可用于阻塞 main 函数
func TestChan(t *testing.T) {
	//select 多路复用  在某些场景下我们需要同时从多个 channel接收数据,这个时候就可以用到select多路复用
	var intChan = make(chan int,10)
	var stringChan = make(chan string,6)
	for i := 0; i < 10; i++ {
		intChan<-i
	}
	for i := 0; i < 6; i++ {
		stringChan<-"hello" +fmt.Sprintf("%d",i)
	}
	//使用select来获取channel里面的数据的时候不需要关闭channel,如果关闭会出现死循环
	for{
		select {
		case v:=<-intChan:
			fmt.Printf("读取int值%v\n",v)
			time.Sleep(time.Millisecond*50)
		case v:=<-stringChan:
			fmt.Printf("读取string值%v\n",v)
			time.Sleep(time.Millisecond * 50)
		default:
			fmt.Println("结束")
			return
		}
	}
}

Goroutine Recover 解决协程中出现的 Panic

func sayNum() {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Println("hello",i)
	}
}
func test()  {
	//defer + recover 处理异常
	defer func() {
		if err:=recover();err!=nil{
			fmt.Println("程序异常")
		}
	}()
	var myMap map[int]string
	myMap[0] = "hello world"
}
func TestChan(t *testing.T) {
	go sayNum()
	go test()
}

你可能感兴趣的:(golang学习)