Go channel结构剖析《三》

简介: Go channel结构剖析《三》

最灵繁的人也看不见自己的背脊。——非洲




使用select可以监控多channel,比如监控多个channel,当其中某一个channel有数据时,就从其读出数据。


一个简单的示例程序如下:


package main
import (
    "fmt"
    "time"
)
func addNumberToChan(chanName chan int) {
    for {
        chanName <- 1
        time.Sleep(1 * time.Second)
    }
}
func main() {
    var chan1 = make(chan int, 10)
    var chan2 = make(chan int, 10)
    go addNumberToChan(chan1)
    go addNumberToChan(chan2)
    for {
        select {
        case e := <- chan1 :
            fmt.Printf("Get element from chan1: %d\n", e)
        case e := <- chan2 :
            fmt.Printf("Get element from chan2: %d\n", e)
        default:
            fmt.Printf("No element in chan1 and chan2.\n")
            time.Sleep(1 * time.Second)
        }
    }
}

程序中创建两个channel:chan1和chan2。函数addNumberToChan()函数会向两个channel中周期性写入数据。通过select可以监控两个channel,任意一个可读时就从其中读出数据。


程序输出如下:


D:\SourceCode\GoExpert\src>go run main.go
Get element from chan1: 1
Get element from chan2: 1
No element in chan1 and chan2.
Get element from chan2: 1
Get element from chan1: 1
No element in chan1 and chan2.
Get element from chan2: 1
Get element from chan1: 1
No element in chan1 and chan2.

从输出可见,从channel中读出数据的顺序是随机的,事实上select语句的多个case执行顺序是随机的,关于select的实现原理会有专门章节分析。


通过这个示例想说的是:select的case语句读channel不会阻塞,尽管channel中没有数据。这是由于case语句编译后调用读channel时会明确传入不阻塞的参数,此时读不到数据时不会将当前goroutine加入到等待队列,而是直接返回。


2 range



通过range可以持续从channel中读出数据,好像在遍历一个数组一样,当channel中没有数据时会阻塞当前goroutine,与读channel时阻塞处理机制一样。简单流程图如下:


func chanRange(chanName chan int) {
    for e := range chanName {
        fmt.Printf("Get element from chan: %d\n", e)
    }
}


注意:如果向此channel写数据的goroutine退出时,系统检测到这种情况后会panic,否则range将会永久阻塞。


3 channel导致goroutine泄漏



3.1 阻塞channel导致的协程泄漏


func examplefunc(handle func(params ...interface{}) (interface{}, error), timeout time.Duration, params ...interface{}) (interface{}, error) {
   if timeout == time.Duration(0) {
      timeout = time.Second
   }
   timer := time.NewTimer(timeout)
   finish := make(chan struct{})
   defer timer.Stop()
   var err error
   var data interface{}
   go func() {
      data, err = handle(params...)
      defer finish <- struct{}{}
   }()
   select {
   case <-timer.C:
      return nil, error_codes.ErrCallTimeOut
   case <-finish:
      return data, err
   }
}


分析:

函数内部启用协程获取handle的返回结果,这里finish作为阻塞管道用于控制后面select的处理,但是当超时或函数内部异常panic时,finish里面的数据无处消费,会导致协程永久阻塞最终导致协程泄漏


结论:

此处将finish修改为非阻塞管道,即finish := make(chan struct{},1)


3.2 3. channel忘记关闭导致的协程泄漏


func bugfunc() {
     taskChan := make(chan int, 100)
     for i := 0; i < 100; i++ { 
         taskChan <- i 
     } 
     consumer := func() { 
         for task := range taskChan { 
             fmt.Println(task) 
         } 
     } 
     for i := 0; i < 100; i++ { 
         go consumer() 
     } 
 }


分析:

go语言中并不强制开发者主动关闭channel(channel也是可以被GC的),但是根据内存回收机制这里的taskChan不会被GC,导致协程泄漏,同时记住for循环的对象如果是channel时只有当channel关闭才会结束


结论:

创建channel对象时要考虑何时关闭channel对象,比如上例子在函数末尾加上close(taskChan)即可解决协程泄漏问题。另外考虑如下对代码的改造:此时对channel的关闭是可有可无的


正确写法:


func bugfunc() {
     taskChan := make(chan int, 100)
     for i := 0; i < 100; i++ { 
         taskChan <- i 
     } 
     consumer := func(d int) { 
         fmt.Println(d) 
     } 
     for i := 0; i < 100; i++ { 
         j <- taskChan
         go consumer(j) 
     } 
     close(taskChan)
 }


3 关注公众号



微信公众号:堆栈future

相关文章
|
11天前
|
Go C语言
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
34 14
|
3月前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
3月前
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
111 50
|
3月前
|
安全 Go 调度
探索Go语言的并发模型:goroutine与channel
在这个快节奏的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你深入了解Go语言的goroutine和channel,这两个核心特性如何协同工作,以实现高效、简洁的并发编程。
|
3月前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
|
3月前
|
程序员 Go
go语言中的控制结构
【11月更文挑战第3天】
123 58
|
4月前
|
存储 Go 调度
深入理解Go语言的并发模型:goroutine与channel
在这个快速变化的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你穿越Go语言的并发世界,探索goroutine的轻量级特性和channel的同步机制。摘要部分,我们将用一段对话来揭示Go并发模型的魔力,而不是传统的介绍性文字。
|
4月前
|
安全 Go 调度
探索Go语言的并发之美:goroutine与channel
在这个快节奏的技术时代,Go语言以其简洁的语法和强大的并发能力脱颖而出。本文将带你深入Go语言的并发机制,探索goroutine的轻量级特性和channel的同步通信能力,让你在高并发场景下也能游刃有余。
|
4月前
|
安全 Go 调度
探索Go语言的并发模型:Goroutine与Channel的魔力
本文深入探讨了Go语言的并发模型,不仅解释了Goroutine的概念和特性,还详细讲解了Channel的用法和它们在并发编程中的重要性。通过实际代码示例,揭示了Go语言如何通过轻量级线程和通信机制来实现高效的并发处理。
|
4月前
|
存储 安全 Go
探索Go语言的并发模型:Goroutine与Channel
在Go语言的多核处理器时代,传统并发模型已无法满足高效、低延迟的需求。本文深入探讨Go语言的并发处理机制,包括Goroutine的轻量级线程模型和Channel的通信机制,揭示它们如何共同构建出高效、简洁的并发程序。

热门文章

最新文章