聊聊协程和管道—管道

开发 前端
使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是仍然可以从该管道读取数据。

管道简介

【1】管道(channel)特质介绍:

(1)管道本质就是一个数据结构-队列

(2)数据是先进先出

(3)自身线程安全,多协程访问时,不需要加锁,channel本身就是线程安全的

(4)管道有类型的,一个string的管道只能存放string类型数据

管道入门案例

【1】管道的定义:

var 变量名 chan 数据类型

PS1:chan管道关键字

PS2:数据类型指的是管道的类型,里面放入数据的类型,管道是有类型的,int类型的管道只能写入整数int

PS3:管道是引用类型,必须初始化才能写入数据,即make后才能使用

【2】案例:

func main()  {
	//定义管道 、 声明管道 ---> 定义一个int类型的管道
	var intChan chan int
	//通过make初始化:管道可以存放3个int类型的数据
	intChan = make(chan int, 3)

	//证明管道是引用类型:
	fmt.Printf("intChan的值: %v \n",intChan)

	//向管道存放数据:
	intChan <- -10
	num := 20
	intChan <- num
	intChan <- 40
	//注意:不能存放大于容量的数据:
	// intChan <- -80
	//输出管道的长度:
	fmt.Printf("管道的实际长度:%v,管道的容量是:%v \n",len(intChan),cap(intChan))

	//在管道中读取数据:
	num1 := <-intChan
	num2 := <-intChan
	num3 := <-intChan
	fmt.Println(num1)
	fmt.Println(num2)
	fmt.Println(num3)

	//注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错:
	// num4 := <-intChan
	// fmt.Println(num4)

	fmt.Printf("管道的实际长度:%v,管道的容量是:%v \n",len(intChan),cap(intChan))
}

管道的关闭

【1】管道的关闭:

使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是仍然可以从该管道读取数据。

【2】案例:

func main()  {
	var intChan chan int
	intChan = make(chan int, 3)
	intChan <- 10
	intChan <- 20
	//关闭管道:
	close(intChan)
	//再次写入数据:--->报错
	// intChan <- 30
	//当管道关闭后,读取数据是可以的:
	num := <- intChan
	fmt.Println(num)
}

管道的遍历

【1】管道的遍历:

管道支持for-range的方式进行遍历,请注意两个细节

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

2)在遍历时,如果管道已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。

【2】案例:

func main()  {
	var intChan chan int
	intChan = make(chan int, 100)
	for i := 0; i < 100; i++ {
		intChan <- i
	}

	//在遍历前,如果没有关闭管道,就会出现deadlock的错误
	//所以我们在遍历前要进行管道的关闭
	// for v := range intChan {
	// 	fmt.Println("value = ",v)
	// }
	close(intChan)
	//遍历:for-range
	for v := range intChan {
		fmt.Println("value = ",v)
	}
}

协程和管道协同工作案例

【1】案例需求:

请完成协程和管道协同工作的案例,具体要求:

1) 开启一个writeData协程,向管道中写入50个整数.

2) 开启一个readData协程,从管道中读取writeData写入的数据。

3) 注意: writeData和readDate操作的是同一个管道

4) 主线程需要等待writeData和readDate协程都完成工作才能退出

【2】原理图:

package main

import (
	"fmt"
	"time"
	"sync"
)

var wg sync.WaitGroup

//写:
func writeData(intChan chan int)  {
	defer wg.Done()
	for i := 1; i <= 50; i++ {
		intChan <- i
		fmt.Println("写入的数据为:",i)
		time.Sleep(time.Second)
	}	
	close(intChan)
}

//读:
func readData(intChan chan int) {
	defer wg.Done()
	for v := range intChan {
		fmt.Println("读取的数据为:",v)
		time.Sleep(time.Second)
	}
}

func main()  {
	//主线程
	//写协程和读协程共同操作同一个管道-》定义管道:
	intChan := make(chan int, 50)
	wg.Add(2)
	//开启读和写的协程:
	go writeData(intChan)
	go readData(intChan)
	//主线程一直在阻塞,什么时候wg减为0了,就停止
	wg.Wait()
	fmt.Println("读写数据完成...")
}

运行结果:

声明只读只写管道

【1】管道可以声明为只读或者只写性质

【2】代码:

package main

import (
	"fmt"
)

func main()  {
	//默认情况下,管道是双向的--》可读可写:
	//声明为只写:
	// 管道具备<- 只写性质
	var intChan chan<- int
	intChan = make(chan int, 3)
	intChan <- 10
	// 报错
	// num := <- intChan
	fmt.Println("intChan:",intChan)

	//声明为只读:
	// 管道具备<- 只读性质 
	var intChan2 <-chan int
	if intChan2 != nil {
		num1 := <- intChan2
		fmt.Println("num1:",num1)
	}
	// 报错
	// intChan2 <- 30
}

管道的阻塞

【1】当管道只写入数据,没有读取,就会出现阻塞:

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup

func writeData(intChan chan int)  {
	defer wg.Done()
	for i := 1; i < 10; i++ {
		intChan <- i
		fmt.Println("写入的数据:",i)
	}
	close(intChan)
}

func readData(intChan chan int)  {
	defer wg.Done()
	for v := range intChan {
		fmt.Println("读取的数据为:",v)
	}
}

func main()  {
	intChan := make(chan int, 10)

	wg.Add(2)
	go writeData(intChan)
	// go readData(intChan)
	wg.Wait()
}

运行结果

【2】写的快,读的慢(管道读写频率不一致),不会出现阻塞问题:

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func writeData(intChan chan int)  {
	defer wg.Done()
	for i := 1; i < 10; i++ {
		intChan <- i
		fmt.Println("写入的数据:",i)
	}
	close(intChan)
}

func readData(intChan chan int)  {
	defer wg.Done()
	for v := range intChan {
		fmt.Println("读取的数据为:",v)
		time.Sleep(time.Second)
	}
}

func main()  {
	intChan := make(chan int, 10)

	wg.Add(2)
	go writeData(intChan)
	go readData(intChan)
	wg.Wait()
}

select功能

【1】select功能:解决多个管道的选择问题,也可以叫做多路复用,可以从多个管道中随机公平地选择一个来执行

PS:case后面必须进行的是io操作,不能是等值,随机去选择一个io操作

PS:default防止select被阻塞住,加入default

【2】代码:

package main

import (
	"fmt"
	"time"
)

func main()  {
	intChan := make(chan int, 1)
	go func ()  {
		time.Sleep(time.Second * 15)
		intChan <- 15
	}()
	stringChan := make(chan string, 1)
	go func ()  {
		time.Sleep(time.Second * 12)
		stringChan <- "hellocyz"
	}()

	//本身取数据就是阻塞的
	// fmt.Println(<-intChan)

	select {
		case v := <-intChan : fmt.Println("intChan:",v)
		case v := <-stringChan : fmt.Println("stringChan:",v)
		default: fmt.Println("防止select被阻塞")
	}
}

defer+recover机制处理错误

【1】问题原因:多个协程工作,其中一个协程出现panic,导致程序崩溃

【2】解决办法:利用defer+recover捕获panic进行处理,即使协程出现问题,主线程仍然不受影响可以继续执行。

【3】案例:

package main

import (
	"fmt"
	"time"
)

//输出数字:
func printNum()  {
	for i := 1; i <= 10; i++ {
		fmt.Println(i)	
	}
}

//做除法操作:
func divide()  {
	defer func ()  {
		err := recover()
		if err != nil {
			fmt.Println("devide()出现错误:",err)
		}
	}()
	num1 := 10
	num2 := 0
	result := num1 / num2
	fmt.Println(result)
}

func main()  {
	//启动两个协程:
	go printNum()
	go divide()
	time.Sleep(time.Second * 5)
}

结果:

责任编辑:武晓燕 来源: 今日头条
相关推荐

2022-11-21 06:55:08

golang协程

2021-02-20 20:36:56

Linux无名管道

2018-09-10 08:45:04

Linux管道命令

2023-11-29 07:10:50

python协程异步编程

2021-09-16 09:59:13

PythonJavaScript代码

2021-04-25 09:36:20

Go协程线程

2014-04-25 10:13:00

Go语言并发模式

2020-02-19 14:16:23

kotlin协程代码

2021-08-04 16:19:55

AndroidKotin协程Coroutines

2023-11-17 11:36:59

协程纤程操作系统

2022-11-14 15:07:09

Linux管道

2014-07-02 21:20:56

CA TechnoloAPI

2020-04-17 08:34:39

Linux管道

2010-10-25 16:52:48

oracle管道函数

2023-10-24 19:37:34

协程Java

2021-12-09 06:41:56

Python协程多并发

2021-05-10 10:40:07

网络攻击Colonial网络安全

2009-08-19 16:36:29

C#管道技术

2023-05-04 16:03:50

KubernetesCI/CD集成

2020-12-09 11:10:12

shellLinux管道
点赞
收藏

51CTO技术栈公众号