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

相关文章
|
7天前
|
Go
go之channel关闭与广播
go之channel关闭与广播
8 0
|
13天前
|
存储 Go
Go 语言当中 CHANNEL 缓冲
Go 语言当中 CHANNEL 缓冲
|
28天前
|
Unix Shell 编译器
Go 中空结构有什么用法
在 Go 语言中,空结构体 struct{} 是一个非常特殊的类型,它不包含任何字段并且不占用任何内存空间。虽然听起来似乎没什么用,但空结构体在 Go 编程中实际上有着广泛的应用。本文将详细探讨空结构体的几种典型用法,并解释为何它们在特定场景下非常有用。
|
7天前
|
Go
go之channel任意任务完成、全部任务完成退出
go之channel任意任务完成、全部任务完成退出
8 0
|
2月前
|
编译器 Go
Go 语言结构
Go 语言结构
18 0
|
2月前
|
负载均衡 Go 调度
使用Go语言构建高性能的Web服务器:协程与Channel的深度解析
在追求高性能Web服务的今天,Go语言以其强大的并发性能和简洁的语法赢得了开发者的青睐。本文将深入探讨Go语言在构建高性能Web服务器方面的应用,特别是协程(goroutine)和通道(channel)这两个核心概念。我们将通过示例代码,展示如何利用协程处理并发请求,并通过通道实现协程间的通信和同步,从而构建出高效、稳定的Web服务器。
|
2月前
|
自然语言处理 数据挖掘 程序员
《Go 简易速速上手小册》第2章:控制结构与函数(2024 最新版)(下)
《Go 简易速速上手小册》第2章:控制结构与函数(2024 最新版)(上)
30 1
|
2月前
|
数据采集 搜索推荐 Go
《Go 简易速速上手小册》第2章:控制结构与函数(2024 最新版)(上)
《Go 简易速速上手小册》第2章:控制结构与函数(2024 最新版)
33 1
|
2月前
|
设计模式 缓存 安全
一篇文章带你吃透Go语言的Atomic和Channel--实战方法
一篇文章带你吃透Go语言的Atomic和Channel--实战方法
48 0
|
2月前
|
Go 开发者 索引
Go语言中的循环控制结构解析
【2月更文挑战第3天】本文将详细探讨Go语言中的循环控制结构,包括`for`循环、`range`循环以及`无限循环`的使用方法和适用场景。通过掌握这些循环结构,Go语言开发者能够更加高效地进行迭代操作、处理集合数据以及实现复杂的控制逻辑。