• golang的channel探索


    1、channel是什么?

    管道或者是通道。字面意思也就是说是传输的通道或者是管道。
    在 Go 语言中,channel 的关键字chan数据流向的表现方式为 <-
    分为两种模式:
    双向– 表现形式为:chan T,即双向通道。
    单向– 表现形式有两种。分别是:chan <- T表示只允许发送的通道,T <- chan表示只允许接收的通道。
    除此之外,channel分为缓冲通道无缓冲通道

    // 无缓冲
    ch1 := make(chan int)
    
    // 缓冲区为 3
    ch2 := make(chan int, 3)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、无缓冲channel

    缓冲区大小默认为0。
    在Go语言中,无缓冲的channel是一种在发送和接收操作之间同步进行的通道。无缓冲channel保证了数据的传递几乎是即时的,发送操作会阻塞,直到另一端的goroutine执行接收操作,反之亦然。这种特性使得无缓冲channel成为goroutine之间同步操作和通信的理想选择。#### 如何创建?

    ch := make(chan int)
    
    • 1
    如何使用?
    var ch = make(chan string)
    	go func() {
    		ch <- "包子"
    	}()
    
    	msg := <-ch
    	fmt.Println(msg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在上面的代码中,发送操作在另一个goroutine中执行,它会阻塞,直到主goroutine执行接收操作。

    特性是什么?

    无缓冲channel的一个关键特性是它们在发送和接收数据时提供了同步保证
    当数据从一个goroutine通过无缓冲channel发送到另一个goroutine时,发送者goroutine会阻塞,直到接收者goroutine接收了数据,这确保了在两个goroutine之间的数据交换是同步的。

    使用场景
    • 需要确保在数据处理过程中两个goroutine能够同步操作时。
    • 用来通知一个goroutine另一个goroutine的事件已经发生,比如任务完成或是需要停止执行
    注意事项

    会发生死锁的两种情况:

    • 没有goroutine接收发送过来的数据
    • 没有goroutine发送数据

    3、有缓冲channel

    和无缓冲相比,多了一个缓冲区域
    无缓冲channel用来同步,有缓冲的channel用来异步
    在Go语言中,有缓冲的channel允许在阻塞发送和接收操作之前存储一个固定数量的值。这种类型的channel是异步的:只有在缓冲区满时发送操作才会阻塞,只有在缓冲区空时接收操作才会阻塞

    如何使用?
    • 当向有缓冲channel发送数据时,如果缓冲区未满,发送操作就会立即完成,并且发送goroutine可以继续执行不会阻塞。如果缓冲区已满,发送操作将会阻塞,直到有空间可用。
    • 从有缓冲channel接收数据时,如果缓冲区中有数据,接收操作会立即提取数据并继续执行。如果缓冲区为空,接收操作将会阻塞,直到有数据可读。
    应用场景是什么?
    • 解耦生产者和消费者的处理速度:当生产者和消费者处理数据的速率不一致时,有缓冲的channel可以作为中间存储,减少直接的依赖
    • 流量控制通过限制缓冲区的大小,可以在一定程度上控制程序的内存使用,防止因为生产速度远大于消费速度而导致的内存溢出
    • 批量处理:有时,等待直到有足够的数据积累在缓冲区中之后再批量处理,可以提高处理效率。
    注意事项
    • 缓冲区大小要合适。太小会导致频繁阻塞,太大会增加内存使用。甚至在生产者速度远大于消费者速度时导致内存泄漏
    • 即使channel是有缓冲的,也应避免在没有接收方时向channel发送数据,这可能会导致发送goroutine永久阻塞,从而造成goroutine泄漏。

    4、如何判断goroutine是否关闭?

    在Go语言中,并没有内置的方法去检测是否关闭
    有3种模式可以间接地帮助理解goroutine是否已经完成了它的执行任务:

    使用sync.WaitGroup
    var wg sync.WaitGroup
    
    	wg.Add(1)
    	go func() {
    		// goroutine完成时调用
    		defer wg.Done()
    		// 执行任务
    	}()
    
    	// 等待所有goroutine全部结束(每个都调用done)
    	wg.Wait()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当wait返回时,表示相关的goroutine已经全部完成了。

    使用channel
    // 根据Go规范,空结构体不分配内存也就是说不占用空间
    // 只是一个信号,没有其他意义
    done := make(chan struct{})
    
    	// 启动一个goroutine等待信号
    	go func() {
    		<-done // 等待信号,不关心传递的数据
    		fmt.Println("Received signal, exiting.")
    	}()
    
    	// 发送信号
    	close(done)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    使用context

    如果你的goroutine是响应取消信号而结束的,那么可以通过检查context的状态来判断goroutine是否已经结束。

    ctx, cancel := context.WithCancel(context.Background())
    
    	go func() {
    		// 等待取消信号
    		<-ctx.Done()
    
    		// 清理并退出
    	}()
    
    	// 发出取消信号
    	cancel()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这种情况下,当cancel被调用时,依赖于这个context的所有goroutine将开始执行退出流程。

    5、如何优雅的关闭goroutine?

    • 使用channel发送退出信号
    done := make(chan struct{})
    
    	go func() {
    		for{
    			select {
    			//等待退出信号
    			case <-done:
    				// 收到信号,退出goroutine
    				return
    			default:
    				// 正常执行任务
    			}
    		}
    	}()
    
    	// 当需要goroutine结束时,关闭channel 并广播给其他goroutine
    	close(done)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 使用context包控制多个goroutine

    如果多个goroutine使用了同一个context,context包提供了一个优雅的解决方案。你可以创建一个context,并在需要停止goroutine时取消它。

    ctx, cancel := context.WithCancel(context.Background())
    
    	go func() {
    		for {
    			select {
    			//检测到取消信号
    			case <-ctx.Done():
    				// 退出goroutine
    				return
    			default:
    				// 正常执行任务
    			}
    		}
    	}()
    
    	//当需要停止goroutine时,调用cancel通知使用该context的所有goroutine停止
    	cancel()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 使用sync.WaitGroup等待goroutine完成优雅关闭
    var wg sync.WaitGroup
    	ctx, cancel := context.WithCancel(context.Background())
    
    	wg.Add(1)
    	go func() {
    		defer wg.Done()
    		for {
    			select {
    			//检测到取消信号
    			case <-ctx.Done():
    				// 退出goroutine
    				return
    			default:
    				// 正常执行任务
    			}
    		}
    	}()
    
    	//通知使用该context的所有goroutine停止
    	cancel()
    	// 等待所有goroutine安全退出
    	wg.Wait()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    ChIP-seq应用领域、研究思路、数据挖掘、下游实验、实验关键 | 易讲堂
    携职教育:来说说中级经济师的利与弊
    SpringMVC 解析(五)URI链接处理
    LabVIEW编程LabVIEW控制C-863.12 水星控制器例程与相关资料
    数据结构——二叉树
    mvc三层架构的思想_使用连接池技术的综合案例
    【C语言基础】Chap. 3. 操作符、关键字、#define和存储
    Redis从入门到放弃(7):主从复制
    SpringBoot+Vue项目招生管理系统
    和运维工程师聊完,发现小丑竟是我自己
  • 原文地址:https://blog.csdn.net/benzun_yinzi/article/details/136343823