Go的Channel知识总结

学习Go语言已有4个月,在此对Go语言相关知识点做个总结


一. Go中的Channel是什么?

Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
Channel是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序,可以类比成Unix 中的进程的通信方式管道。
使用channel可以方便地在各goroutine之间传递数据,保证了各协程的通信。

二. Channel语法

1.定义和创建Channel

var ch chan int //定义一个int类型的channel
var ch = make(chan string, 5) //创建一个传递string类型的channel,缓冲为5

2.操作Channel

ch := make(chan int, 5) //先定义一个传递int类型的缓冲为5的channel
ch <- 8  //将8发送到ch中
a := <- ch  //用变量a来接收
<- ch  //从ch中接受,忽略结果
close(ch)  // 关闭channel

对于关闭channel需要注意:

  • 对关闭后的channel进行发送数据操作会导致panic
  • 对关闭后的channel进行读数据操作会一直接收值,直到channel中无数据
  • 对一个已关闭的无数据的channel进行读操作会接收到对应类型的0值,如int类型接收到0string类型接收到""
  • 关闭一个已经关闭的channel会导致panic

3.Channel的缓冲

Channel分为有缓冲和无缓冲两种

  • 无缓冲:
ch := make(chan int)
ch <- 8
fmt.Println("发送数据成功")

上面代码执行结果会报错,不会打印"发送数据成功",因为ch通道没有缓冲,对其发送数据后,协程便会一直阻塞在此,除非另一个协程对ch进行读取操作,才会继续执行后面的代码

  • 有缓冲
ch := make(chan int, 2)  // ch缓冲为2

一般使用for range结构对channel进行读取:

ch := make(chan int, 3) // 定义一个缓冲为3的channel
for i := 0; i < 3; i++ {
	ch <- i //向ch中连续发送3个数据
}
close(ch)  // 别忘了关闭channel
// 使用for range读取channel,当channel中数据被读完后,会自动退出for循环
for v := range ch {
	fmt.Println(v)  // 打印结果分别为 0  1  2
}

// 再对已关闭的ch进行直接读取
fmt.Println(<- ch) // 打印结果为 0,即int类型对应的零值
//再次对ch进行读取
a, ok := <- ch
if ok {
		fmt.Println(a)
	} else {
		fmt.Println("接收失败,channel已经关闭")  // 会打印该条语句,因为ch已经关闭
}

//此时再对ch发送数据
ch <- 8 // 会报错:panic: send on closed channel

4. 单向Channel和双向Channel

其实channel都是双向的,单向channel主要用于方法声明,防止channel被滥用。
一个单独存在单向channel毫无意义的。
示例代码:

func example(ch <- chan int){  //传入channel,channel在该函数中只能向外发送数据
	for {
		select{
		case <- ch:
			fmt.Println("你好")
		}
	}
	
	ch <- 1  // 会报错
}

func main() {
	ch := make(chan int, 1)  //定义一个双向channel
	example(ch)
}

三. 使用场景

1. 用于goroutine之间的消息传递:

func work(ch chan struct{}){
	fmt.Println("任务执行中......")
	time.Sleep(1*time.Second)
	ch <- struct{}{}               // 传递消息给通道
}

func main() {
	ch := make(chan struct{})
	go work(ch)
	<- ch                         // 阻塞等待消息
	fmt.Println("任务完成!")
}

输出结果:

任务执行中......
任务完成!

进程 已完成,退出代码为 0

2. 用于goroutine之间数据传递

如交替打印数字和字母:

func num(ch chan int,wg *sync.WaitGroup) {
	for i := range ch {
		fmt.Printf("%d%d",i,i+1)
		ch <- i + 1
		wg.Done()
	}
}

func char(ch chan int,wg *sync.WaitGroup) {
	for i := range ch {
		fmt.Print(string(63+i), string(64+i))
		ch <- i + 1
		wg.Done()
	}
}

func main() {
	ch := make(chan int)
	wg := &sync.WaitGroup{}
	wg.Add(26)
	go num(ch,wg)
	ch <- 1
	go char(ch,wg)
	wg.Wait()
}

3. 控制并发数量

func main() {
	jobCount := 38
	wg := sync.WaitGroup{}
	wg.Add(jobCount)
	limit := make(chan struct{},10)       // 并发数量限制为10
	for i := 0; i < jobCount; i++ {
		limit <- struct{}{}
		go func() {
			fmt.Println("任务执行中")
			time.Sleep(1*time.Second)
			<- limit
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println("任务完成")
}

4. 超时控制

func work(ch chan struct{}) <-chan struct{} {
	go func() {
		fmt.Println("开始任务")
		time.Sleep(3 * time.Second)
		ch <- struct{}{}
	}()
	return ch
}

func main() {
	var ch chan struct{}
	select {
	case <-work(ch):
		fmt.Println("任务准时完成")
	case <-time.After(1 * time.Second):
		fmt.Println("任务超时")
	}
}

四. Channel注意事项

1.len和cap的区别

  • lenchannel中已有的数据长度,capchannel定义时的容量,即缓冲
ch1 := make(chan int, 3)
ch <- 9
ch <- 6
fmt.Println(len(ch1))  // 输出 2
fmt.Println(cap(ch1))  // 输出 3
ch2 := make(chan int)
fmt.Println(cap(ch2))  // 输出 0

2.可能存在的危险

1.使用channel不当造成阻塞

示例一:

func main() {
	ch := make(chan int)
	ch <- 1
	fmt.Println(<- ch)
}

运行报错fatal error: all goroutines are asleep - deadlock!
因为ch是无缓冲channel,当c接收了值后,必须要有另一个goroutine对ch进行读取,程序才能进行下去,否则阻塞。

示例二:

func main() {
	ch := make(chan int, 1)
	ch <- 1
	ch <- 2
	fmt.Println(<- ch)
}

该情况也会运行报错,因为原因同上

2. channel未关闭

func main() {
	ch := make(chan int,2)
	go func(ch chan int){
		ch <- 1
		ch <- 3
	}(ch)
	for v := range ch {
		fmt.Println(v)
	}
}

报错fatal error: all goroutines are asleep - deadlock!
channel未关闭导致的错误,应该在向channel发送数据完成后对其用close()函数进行关闭

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枫花海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值