44-Golang中的channel

为什么要使用channel

前面使用全局变量加锁同步来解决goroutine的通讯,但不完美

  • 1.主线程在等待所有goroutine全部完成时间很难确定,我们这里设置10秒,仅仅是过段
  • 2.如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有routine处于工作状态,这时也会随主线程的退出而销毁
  • 3.通过全局白能量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作
  • 4.上面种种分析都在互换一个新的通讯机制-----channel

channel的介绍

  • 1.channel本质就是一个数据结构-队列
  • 2.数据是先进先出
  • 3.线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
  • 4.channel是有类型的,一个string的channel只能存放string类型数据
    在这里插入图片描述

channel的基本使用

定义/声明channel

var 变量名chan数据类型

举例:

var intChan chan int (intChan用于存放int数据)

var mapChan chan map[int]ssting (mapChan用于存放map[int]string类型)

var perChan chan Person

var perChan2 chan *Person

说明:

channel是引用类型

channel必须初始化才能写入数据,即make后才能使用

管道是有类型的,intChan只能写入整数

package main

import "fmt"

func main() {
	//演示一下管道的使用
	//创建一个可以存放三个int类型的俄管道
	var intChan chan int
	intChan = make(chan int, 3)

	//看看intChan是什么
	fmt.Printf("intChan的值=%v intChan本身的地址=%p\n", intChan, &intChan)

	//向管道写入数据
	intChan <- 10
	num := 211
	intChan <- num

	//看看管道的长度和cap(容量)
	fmt.Printf("channel len = %v cap=%v \n", len(intChan), cap(intChan))

	//从管道中读取数据
	var num2 int
	num2 = <-intChan
	fmt.Println("num2=", num2)
	fmt.Printf("channel len =%v cap=%v \n", len(intChan), cap(intChan))

	//在没有使用协程的情况下,如果我们管道数据已经全部取出,在取就会报告deadlock
}
/*
intChan的值=0xc00010e080 intChan本身的地址=0xc000006028
channel len = 2 cap=3 
num2= 10              
channel len =1 cap=3  
*/

管道的遍历和关闭

channel的关闭

使用内置函数close可以关闭channel,当channel关闭后,就不能再想channel写数据了,但是仍然可以从给channel读取数据

channel的遍历

channel支持for-range的方式进行遍历

1.在遍历时,如果channel没有关闭,则会出现deadlock的错误

2.在遍历时,如果channel已经关闭,则会出现正常遍历数据,遍历完后,就会退出遍历

package main

import "fmt"

func main() {
	intChan := make(chan int, 3)
	intChan <- 100
	intChan <- 200
	close(intChan)
	//这时不能够再写入到channel
	//intChan <- 300
	fmt.Println("okk")
	n1 := <-intChan
	fmt.Println("n1=", n1)

	//遍历管道
	intChan2 := make(chan int, 100)
	for i := 0; i < 100; i++ {
		intChan2 <- i * 2 //放入100个数据到管道
	}
	// 在遍历时,如果channel没有关闭,责护出现deadlock的错误
	close(intChan2)
	for v := range intChan2 {
		fmt.Println("v=", v)
		
	}
    
}

goroutine和channel结合

应用实例1

在这里插入图片描述

在这里插入图片描述

package main

import (
	"fmt"
	"time"
)

func writeData(intChan chan int) {
	for i := 1; i < 50; i++ {
		//放入数据
		intChan <- i
		fmt.Println("writeData", i)
		time.Sleep(time.Second)
	}
	close(intChan) //关闭
}

//read data
func readData(intChan chan int, exitChan chan bool) {
	for {
		v, ok := <-intChan
		if !ok {
			break
		}
		time.Sleep(time.Second)
		fmt.Printf("readData读到数据=%v\n", v)
	}
	//readData读取完数据后,即任务完成
	exitChan <- true
	close(exitChan)
}

func main() {
	//创建两个管道
	intChan := make(chan int, 50)
	exitChan := make(chan bool, 1)

	go writeData(intChan)
	go readData(intChan, exitChan)

	//time.Sleep(time.Second * 10)
	for {
		_, ok := <-exitChan
		if !ok {
			break
		}
	}
}

应用实例2

如果只是向管道写入数据,而没有读取,就会出现阻塞而dead lock,原因是intChan容量是10,而带点吗writeData会写入50个数据,因此就会阻塞在writeData的ch<-i

案例

在这里插入图片描述

package main

import "fmt"

func putNum(intChan chan int) {
	for i := 1; i <= 8000; i++ {
		intChan <- i
	}
	//关闭intChan
	close(intChan)
}

//开启四个协程,从intChan取出数据,并判断是否为素数
//如果是,就放入到primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
	//使用for循环
	var flag bool
	for {
		num, ok := <-intChan
		if !ok {
			break
		}
		flag = true //假定是素数
		//判断num是不是素数
		for i := 2; i < num; i++ {
			if num%i == 0 { //说明该num不是素数
				flag = false
				break
			}
		}
		if flag {
			//将这个数就放入到primeChan
			primeChan <- num
		}
	}
	fmt.Println("有一个primeNum协程因为取不到数据,退出")
	//这里还不能关闭primeChan
	//向exitChan写入true
	exitChan <- true
}

func main() {
	intChan := make(chan int, 1000)
	primeChan := make(chan int, 2000) //放入结果
	//标识退出的管道
	exitChan := make(chan bool, 4)

	//开启一个协程,想in特产放入1-8000个数
	go putNum(intChan)
	//开启四个协程,从intChan取出数据,并判断是否为素数
	//如果是,就放入到primeChan
	for i := 0; i < 4; i++ {
		go primeNum(intChan, primeChan, exitChan)
	}

	//这里进行主线程处理
	go func() {
		for i := 0; i < 4; i++ {
			<-exitChan
		}

		//当我们从exitChan,去出了4个结果,就可以放心关闭primeChan
		close(primeChan)
	}()

	//遍历primeNum,把结果输出
	for {
		res, ok := <-primeChan
		if !ok {
			break
		}
		//将结果输出
		fmt.Printf("素数=%d\n", res)
	}
	fmt.Println("main线程退出")
}

注意事项

  • 1.channel可以声明为只读,或者只写性质
  • 2.channel只读和只写的最佳实践案例
package main

import "fmt"

func main() {
	//管道可以声明为只读或者只写
	//默认情况下,管道是双向的
	//var chan1 chan int

	//声明为只写
	var chan2 chan<- int
	chan2 = make(chan int, 3)
	chan2 <- 20
	//num := <-chan2
	fmt.Println("chan2=", chan2)

	//声明为只读
	var chan3 <-chan int
	num2 := <-chan3
	fmt.Println("num2=", num2)
}

  • 3.使用select可以解决从管道取数据的阻塞问题
  • 4.goroutine中使用recover,解决携程中出现panic,导致程序奔溃问题
    • 说明,如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这是我们可以在goroutine中使用recover来捕获panic,进行处理,这样及时这个协程发生的问题,但是主线程仍然不受影响,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值