Go 协程通道使用注意 golang channel有无缓冲区的区别

目录

关闭channel

引入

 不关闭通道是有风险的,主要存在两条:

如何优雅的关闭?

1.只有一个发送者

2.多个发送者

判断通道是否关闭

有缓存和无缓存的通道有什么区别?

错误的关闭通道


关闭channel

close(chan):关闭通道并不会丢失里面的数据,只是让读取通道数据的时候不会读完之后一直阻塞等待新数据写入。

引入

看一看下面的例子:

ch := make(chan int, 1)
ch <- 11
// close(ch) // 如果不关闭,下面会引发死锁

// 通道没有关闭的时候会阻塞,程序会永远等待从通道中接收值。而且由于没有其他goroutine来关闭通道,这个阻塞状态将会持续下去,从而引发死锁。
for v := range ch {
    fmt.Println(v)
}

 不关闭通道是有风险的,主要存在两条:

  1. 出现死锁:如果通道不会再被使用但未关闭,接收者可能会一直等待数据,导致死锁或无意义的等待。
  2. 出现资源泄漏:未关闭的通道可能会导致资源泄漏,因为垃圾收集器不会回收未关闭的通道。而且还要消耗电脑的性能来维护这条管道。

如何优雅的关闭?

在关闭管道的时候,我们要注意:正确地决定何时关闭通道、确保通道不会被多次关闭。(多次关闭同一个通道会报panic)

1.只有一个发送者

如果只有一个发送者,那么这个发送者在完成所有发送操作后可以直接关闭通道:

package main

import (
	"fmt"
	"time"
)

// 边入边出
func main() {
	c := make(chan int, 5)
	go fibonacci(10, c)

	for v := range c {
		fmt.Println("out:", time.Now())
		fmt.Println(v)
	}
}

// 应该由发送数据的一方关闭通道,当数据发送完毕后就是关闭通道的时候。
func fibonacci(n int, c chan int) {
	x, y := 0, 1

	for i := 0; i < n; i++ {
		c <- x
		fmt.Println("in:", time.Now())
		time.Sleep(100)
		x, y = y, x+y
	}
	close(c)
}

// 大致运行结果:
// in: 2024-07-16 16:00:34.637655 +0800 CST m=+0.000171297
// out: 2024-07-16 16:00:34.637681 +0800 CST m=+0.000197020
// in: 2024-07-16 16:00:34.637952 +0800 CST m=+0.000467725
// 0
// in: 2024-07-16 16:00:34.637963 +0800 CST m=+0.000478508
// out: 2024-07-16 16:00:34.637968 +0800 CST m=+0.000483915
// 1
// in: 2024-07-16 16:00:34.637976 +0800 CST m=+0.000491801
// in: 2024-07-16 16:00:34.637983 +0800 CST m=+0.000499051
// out: 2024-07-16 16:00:34.637978 +0800 CST m=+0.000494157
// 1
// out: 2024-07-16 16:00:34.637999 +0800 CST m=+0.000514485
// 2
// out: 2024-07-16 16:00:34.638006 +0800 CST m=+0.000522241
// 3
// out: 2024-07-16 16:00:34.638013 +0800 CST m=+0.000528591
// 5
// in: 2024-07-16 16:00:34.637988 +0800 CST m=+0.000504017
// in: 2024-07-16 16:00:34.638039 +0800 CST m=+0.000555021
// in: 2024-07-16 16:00:34.638045 +0800 CST m=+0.000561340
// in: 2024-07-16 16:00:34.638049 +0800 CST m=+0.000564725
// in: 2024-07-16 16:00:34.638055 +0800 CST m=+0.000570642
// out: 2024-07-16 16:00:34.638052 +0800 CST m=+0.000568007
// 8
// out: 2024-07-16 16:00:34.638095 +0800 CST m=+0.000610606
// 13
// out: 2024-07-16 16:00:34.638103 +0800 CST m=+0.000619251
// 21
// out: 2024-07-16 16:00:34.63811 +0800 CST m=+0.000626188
// 34

2.多个发送者

当有多个发送者时,可以使用 sync.WaitGroup 来协调这些发送者,并在所有发送者完成后由一个协程关闭通道:

package main

import (
	"fmt"
	"sync"
)

func main() {
	ch := make(chan string)
	var wg sync.WaitGroup

	// 启动3个协程发送数据
	for i := 1; i <= 5; i++ {
		wg.Add(1)

		go func(num int) {
			defer wg.Done()
			for j := 'a'; j <= 'e'; j++ {
				ch <- fmt.Sprintf("协程%d:%c", num, j)
			}
		}(i)
	}

	// 启动一个协程来等待所有发送者完成并关闭通道
	go func() {
		wg.Wait()
		close(ch)
	}()

	// 接收数据
	for val := range ch {
		fmt.Println(val)
	}
	fmt.Println("通道已关闭,结束接收")
}

判断通道是否关闭

v, ok := <-ch

如果 ok 为 true,表示成功从 channel 中接收到一个值,并且 channel 还没有关闭。如果 ok 为 false,表示 channel 已经关闭。

当一个 channel 已经关闭而且其中的元素已经全部被取出时,再从管道中取出数据会返回该元素类型的零值,并且 ok 会被设置为 false。这样的检查是为了防止在已关闭的 channel 上进行接收操作时,引发 panic。因为在已关闭的 channel 上进行接收操作会立即返回零值,但如果不进行检查,可能会误认为是从 channel 中接收到了有效的数据。因此,通过检查 ok,我们可以确定是否成功接收到了有效的值。

    c := make(chan int, 5)
    c <- 1
	c <- 2
	c <- 3
	close(c)

	v1, ok := <-c
	fmt.Println(v1, ok) // 1 true
	v1, ok = <-c
	fmt.Println(v1, ok) // 2 true
	v1, ok = <-c
	fmt.Println(v1, ok) // 3 true
	v1, ok = <-c
	fmt.Println(v1, ok) // 0 false
	v1, ok = <-c
	fmt.Println(v1, ok) // 0 false

有缓存和无缓存的通道有什么区别?

参考下面链接:

golang channel有无缓冲区的区别

错误的关闭通道

错误一:多次关闭同一个通道

    c := make(chan int, 5)
	for i := 0; i < 5; i++ {
		defer close(c) // 报panic:同一个通道关闭了多次
	}

错误二:资源泄漏

    for i := 0; i < 5; i++ {
		c := make(chan int, 5)
		defer close(c)
	}

看起来没有错误,运行出来也没有错误。但是可能会导致资源泄漏,因为每次循环迭代中创建的通道不会立即关闭,导致内存和其他资源的泄漏。这五个管道的关闭时间都是在:所在函数运行完毕的时候关闭的。而不是本次循环结束后就立刻关闭。

相关推荐

  1. Go缓冲channel区别

    2024-07-17 09:08:06       26 阅读
  2. Go缓冲通道与容量为1缓冲通道区别

    2024-07-17 09:08:06       32 阅读
  3. Go缓冲通道限制并发数目

    2024-07-17 09:08:06       31 阅读
  4. Go通道综合应用问题

    2024-07-17 09:08:06       23 阅读
  5. Go语言使用

    2024-07-17 09:08:06       57 阅读
  6. Go实现简单池(通过channel实现)

    2024-07-17 09:08:06       34 阅读
  7. go

    2024-07-17 09:08:06       30 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 09:08:06       70 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 09:08:06       74 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 09:08:06       62 阅读
  4. Python语言-面向对象

    2024-07-17 09:08:06       72 阅读

热门阅读

  1. 达梦数据库-学习

    2024-07-17 09:08:06       26 阅读
  2. golang系统文件路径与文件打开问题

    2024-07-17 09:08:06       23 阅读
  3. 【问题记录】线程池死锁问题

    2024-07-17 09:08:06       25 阅读
  4. 【工具类】对象比较工具类实现

    2024-07-17 09:08:06       21 阅读
  5. Python3 第二十四课 -- 模块

    2024-07-17 09:08:06       20 阅读
  6. 你不需要 CSS 框架

    2024-07-17 09:08:06       24 阅读
  7. 使用 RocketMQ 实现消息的顺序消费

    2024-07-17 09:08:06       26 阅读
  8. c#之修饰符知识点

    2024-07-17 09:08:06       25 阅读
  9. Conda的冲突解决艺术:在包依赖中寻找和谐

    2024-07-17 09:08:06       27 阅读
  10. zookeeper+kafka群集

    2024-07-17 09:08:06       29 阅读