0
点赞
收藏
分享

微信扫一扫

Go语言channel

小磊z 2022-03-30 阅读 86

在这里插入图片描述

Go语言channel

1、什么是channel

  • channel是golang在goroutine之间的通讯方式
  • channel是引用类型,使用的时候必须通过make进行初始化,makechannel打印结果是地址

2、为什么需要channel

3、channel的基本介绍

  • 1、channel的本质就是一个数据结果-队列

  • 2、数据是先进先出

  • 3、线程安全,多个goroutine访问时,不需要加锁,也就是channel本身就是线程安全的(因为管道本身就是阻塞模式的,遵循先进先出的概念,所以channel本身就是安全的

  • 4、示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTVQ9jtA-1647748910509)(../image/pic_center.png)]

  • 5、channel的注意事项
  • channel中只能存放指定的数据类型
  • channle的数据放满后,就不能再放入了
  • 如果从 channel 取出数据后,可以继续放入
  • 在没有使用协程的情况下,如果channel数据取完了,再取,就会报 dead lock
  • channel接收参数结束后,必须关闭,避免channel不断增加带来的内存泄漏
  • 关闭channel的时候不要在接收端关闭channel
  • 6、理解和总结

引用类型的例子

package main

import "fmt"

func main() {
 var intChan chan int
 intChan = make(chan int, 3)
 //2. 看看 intChan 是什么
 fmt.Printf("intChan  的值=%v intChan 本身的地址=%p\n", intChan, &intChan)
}

运行结果
在这里插入图片描述

上面例子可以看出intChan是地址,输出地址的时候,指向另一片地址。也即是它是引用类型。

4、channel的使用

4.1、概念

4.2、创建channel

make(chan 元素类型, 容量)

(1)创建channel

ch := make(chan int, 3)	//申明channel

(2)发送

ch <- 10	//把10传递给ch

(3)接收

x := <-ch	//从ch中接收值并赋值给变量x

(4)关闭管道

close(ch)  //关闭管道

(5)实例分析

4.3、实例分析

package main

import "fmt"

func main() {
	chan1 := make(chan int, 3)
	chan1 <- 11
	chan1 <- 20
	chan1 <- 30
	a := <-chan1
	fmt.Println(a) //打印的结果是11
}

执行结果
在这里插入图片描述

package main

import "fmt"

func main() {
	chan1 := make(chan int, 3)
	chan1 <- 11
	chan1 <- 20
	chan1 <- 30
	a := <-chan1
	fmt.Println(a) //11
	b := <-chan1
	fmt.Println(b) //20
	c := <-chan1
	fmt.Println(c) //30
}

执行结果

在这里插入图片描述

4.3.1、channel模拟消息队列

package main

import (
	"fmt"
)

func main() {
	fmt.Println("run in main coroutine.")

	count := 10
	c := make(chan bool, count)

	for i := 0; i < count; i++ {
		go func(i int) {
			fmt.Printf("run in child coroutine %d.\n", i)
			c <- true
		}(i)
	}

	for i := 0; i < count; i++ {
		<-c
	}
}

执行结果
在这里插入图片描述

4.3.2、区分缓冲和非缓冲

chInt := make(chan int) 		//非缓冲通道
chBool := make(chan bool, 0) 	//非缓冲通道
chStr := make(chan string, 2)	//缓冲通道

如:

package main

import "fmt"

func main()  {
	ch1 := make(chan string)
	ch1 <- "hello"
	fmt.Println(<-ch1)
}
//报错:fatal error: all goroutines are asleep - deadlock!
// 致命的错误,所有goroutines都在休眠状态-死锁
//我们可以理解为,非缓冲channel必须另起一个groutine才能启动

综上所述必须另起一个groutine才可以

package main

import "fmt"

func main()  {
	ch1 := make(chan string)
	go func() {
		ch1 <- "hello"
	}()
	fmt.Println(<-ch1)
}
//输出结果hello

在这里插入图片描述

如果是缓冲channel同样也不报错

package main

import "fmt"

func main() {
	ch1 := make(chan string, 1)
	ch1 <- "hello"
	fmt.Println(<-ch1)
}
//输出结果hello

在这里插入图片描述

非缓冲通道必须同时有两个groutine对它读和写

4.3.3、单向接口的遍历

package main

import "fmt"

func recive(ch1 chan <- int)  {
	for i := 1; i<=5; i++ {
		ch1 <- i
	}
	close(ch1) //这一句去掉将会报错
}

func customer(ch1 <- chan int)  {
	for data := range ch1 {
		fmt.Println(data)
	}
}

func main()  {
	c := make(chan int)
	//创建协程
	go recive(c)
	customer(c)
	fmt.Println("done")
}
//遍历的时候必须关闭当前的channel,否则系统不知道什么时候终止遍历的状态

管道重新赋值后,发现改变了原有的,这是因为,通道本身是引用类型的缘故

func main() {
    ch1 := make(chan int, 4)
    ch1 <- 30
    ch1 <- 40
    ch1 <- 50
    ch2 := ch1
    ch2 <- 100
    <-ch1
    <-ch1
    <-ch1
    d := <-ch1
    fmt.Println(d)
}

4.4、关于close通道

close关闭通道是必须的,避免其它进程针对当前这次的channel继续进行塞入数据而出现的混淆数据、内存溢出的现象,所以无论是缓冲还是非缓存,在接收数据完成后,都进行close一下

var ch1 = make(chan int, 10)
	for i := 1; i <=10; i++ {
		ch1 <- i
	}
	close(ch1)//写入完成后关闭

如果未关闭,下个用户继续访问,而channel的内部是个队列数据结构的,会不断的往队列里填充数据的。

5、for … select操作类似于swtich

package main

import "fmt"

func toString(ch chan string)  {
	fmt.Println("to string")
	ch <- "hello"
}

func toInt(ch chan int)  {
	fmt.Println("to int")
	ch <- 100
}

func main()  {
	chI := make(chan int)
	chS := make(chan string)
	go toString(chS)
	go toInt(chI)

	for i := 0; i < 2; i++ {
		select {
		case <- chI:
			fmt.Println("get value from int")
		case <- chS:
			fmt.Println("get value from string")

		}
	}
}
/*
** 打印结果:
** to int
** get value from int
** to string
** get value from string
*/

6、channel循环遍历的坑

channel循环中的变量不关闭为什么报错?

package main

import "fmt"

func recive(ch1 chan <- int)  {
	for i := 1; i<=5; i++ {
		ch1 <- i
	}
  //close(ch1)
}

func customer(ch1 <- chan int)  {
	for data := range ch1 {//管道遍历的时候,是没有key的
		fmt.Println(data)
	}
}

func main()  {
	c := make(chan int)
	//创建协程
	go recive(c)
	customer(c)
	fmt.Println("done")
}
//运行的时候报错
//fatal error: all goroutines are asleep - deadlock!
//goroutine 1 [chan receive]:
//main.customer(0xc00008c060)

recive方法里加上close(ch1)后才运行正常,

实际上我没理解for range channel

for range channel不知道channel实际有多少个元素,我以为是我往里面接收多少数据,它就能遍历多少元素出来,实际上,不关闭channel的接收,它会一直遍历下去。当它遍历到第6个元素的时候就已经阻塞了当前的main函数

基于此我们改一下:

package main

import "fmt"

func recive(ch1 chan <- int)  {
	for i := 1; i<=5; i++ {
		ch1 <- i
	}
}

func customer(ch1 <- chan int)  {
	for i := 1; i<=5; i++ {
		fmt.Println(<-ch1)
	}
}

func main()  {
	c := make(chan int)
	//创建协程
	go recive(c)
	customer(c)
	fmt.Println("done")
}

这样就不报错了,实际中注入写入完成后注意关闭channel通道,做到避免死锁的产生。

在这里插入图片描述

举报

相关推荐

0 条评论