您现在的位置是:群英 > 开发技术 > 编程语言
Go中select死锁的问题怎么解决
Admin发表于 2022-07-19 18:04:59635 次浏览
这篇文章主要介绍了title,小编觉得挺不错的,现在分享给大家,也给大家做个参考,希望大家通过这篇文章可以有所收获。

目录

    下面对是一个 select 死锁的问题

    package main
    
    import "sync"
    
    func main() {
     var wg sync.WaitGroup
     foo := make(chan int)
     bar := make(chan int)
     wg.Add(1)
     go func() {
      defer wg.Done()
      select {
      case foo <- <-bar:
      default:
       println("default")
      }
     }()
     wg.Wait()
    }
    

    按常规理解,go func 中的 select 应该执行 default 分支,程序正常运行。但结果却不是,而是死锁。可以通过该链接测试:https://play.studygolang.com/p/kF4pOjYXbXf。

    原因文章也解释了,Go 语言规范中有这么一句:

    For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the “select” statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.

    不知道大家看懂没有?于是,最后来了一个例子验证你是否理解了:为什么每次都是输出一半数据,然后死锁?(同样,这里可以运行查看结果:https://play.studygolang.com/p/zoJtTzI7K5T)

    package main
    
    import (
     "fmt"
     "time"
    )
    
    func talk(msg string, sleep int) <-chan string {
     ch := make(chan string)
     go func() {
      for i := 0; i < 5; i++ {
       ch <- fmt.Sprintf("%s %d", msg, i)
       time.Sleep(time.Duration(sleep) * time.Millisecond)
      }
     }()
     return ch
    }
    
    func fanIn(input1, input2 <-chan string) <-chan string {
     ch := make(chan string)
     go func() {
      for {
       select {
       case ch <- <-input1:
       case ch <- <-input2:
       }
      }
     }()
     return ch
    }
    
    func main() {
     ch := fanIn(talk("A", 10), talk("B", 1000))
     for i := 0; i < 10; i++ {
      fmt.Printf("%q\n", <-ch)
     }
    }
    
    

    有没有这种感觉:

    这是 StackOverflow 上的一个问题:https://stackoverflow.com/questions/51167940/chained-channel-operations-in-a-single-select-case。

    关键点和文章开头例子一样,在于 select case 中两个 channel 串起来,即 fanIn 函数中:

    select {
    case ch <- <-input1:
    case ch <- <-input2:
    }
    
    
    

    如果改为这样就一切正常:

    select {
    case t := <-input1:
      ch <- t
    case t := <-input2:
      ch <- t
    }
    
    

    结合这个更复杂的例子分析 Go 语言规范中的那句话。

    对于 select 语句,在进入该语句时,会按源码的顺序对每一个 case 子句进行求值:这个求值只针对发送或接收操作的额外表达式。

    比如:

    // ch 是一个 chan int;
    // getVal() 返回 int
    // input 是 chan int
    // getch() 返回 chan int
    select {
      case ch <- getVal():
      case ch <- <-input:
      case getch() <- 1:
      case <- getch():
    }
    
    

    在没有选择某个具体 case 执行前,例子中的 getVal() <-input getch() 会执行。这里有一个验证的例子:https://play.studygolang.com/p/DkpCq3aQ1TE。

    package main
    
    import (
     "fmt"
    )
    
    func main() {
     ch := make(chan int)
     go func() {
      select {
      case ch <- getVal(1):
       fmt.Println("in first case")
      case ch <- getVal(2):
       fmt.Println("in second case")
      default:
       fmt.Println("default")
      }
     }()
    
     fmt.Println("The val:", <-ch)
    }
    
    func getVal(i int) int {
     fmt.Println("getVal, i=", i)
     return i
    }
    
    

    无论 select 最终选择了哪个 casegetVal() 都会按照源码顺序执行: getVal(1) getVal(2)也就是它们必然先输出:

    getVal, i= 1
    getVal, i= 2
    
    

    你可以仔细琢磨一下。

    现在回到 StackOverflow 上的那个问题。

    每次进入以下 select 语句时:

    select {
    case ch <- <-input1:
    case ch <- <-input2:
    }
    
    
    

    <-input1 和 <-input2 都会执行,相应的值是:A x 和 B x(其中 x 是 0-5)。但每次 select 只会选择其中一个 case 执行,所以 <-input1 和 <-input2 的结果,必然有一个被丢弃了,也就是不会被写入 ch 中。因此,一共只会输出 5 次,另外 5 次结果丢掉了。(你会发现,输出的 5 次结果中,x 比如是 0 1 2 3 4)

    main 中循环 10 次,只获得 5 次结果,所以输出 5 次后,报死锁。

    虽然这是一个小细节,但实际开发中还是有可能出现的。比如文章提到的例子写法:

    // ch 是一个 chan int;
    // getVal() 返回 int
    // input 是 chan int
    // getch() 返回 chan int
    select {
      case ch <- getVal():
      case ch <- <-input:
      case getch() <- 1:
      case <- getch():
    }
    
    

    因此在使用 select 时,一定要注意这种可能的问题。

    不要以为这个问题不会遇到,其实很常见。最多的就是 time.After 导致内存泄露问题,网上有很多文章解释原因,如何避免,其实最根本原因就是因为 select 这个机制导致的。

    比如如下代码,有内存泄露(传递给 time.After 的时间参数越大,泄露会越厉害),你能解释原因吗?

    package main
    
    import (
        "time"
    )
    
    func main()  {
        ch := make(chan int, 10)
    
        go func() {
            var i = 1
            for {
                i++
                ch <- i
            }
        }()
    
        for {
            select {
            case x := <- ch:
                println(x)
            case <- time.After(30 * time.Second):
                println(time.Now().Unix())
            }
        }
    }
    
    



    以上就是关于Go中select死锁的问题怎么解决的介绍啦,需要的朋友可以参考上述内容,希望对大家有帮助,欢迎关注群英网络,小编将为大家输出更多高质量的实用文章!

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

    标签: Go select死锁
    相关信息推荐
    2022-09-06 17:51:22 
    摘要:本篇文章给大家带来了关于javascript的相关知识,其中主要介绍了关于异常处理的相关问题,编写程序的过程难免会出现一些错误,通过这些产生的错误,我们可以学会如何避免遇到这样的情况,以及如何在下次做的更好,下面一起来看一下,希望对大家有帮助。
    2021-12-06 18:17:50 
    摘要:这篇文章主要给大家分享的是Python列表,集合和字典生成式的实现,具有一定的借鉴价值,感兴趣的朋友可以参考,希望大家阅读完这篇文章能有所收获,下面我们一起来学习一下吧。
    2021-12-21 17:49:38 
    摘要:这篇文章我们来了解C语言的scanf和scanf_s函数用法,一些朋友会认为scanf和scanf_s函数是一样的,其实两者有一定的区别,下文我们就来详细的了解看看,有需要的朋友可以参考,那么接下来就跟随小编来了解看看吧!
    云活动
    推荐内容
    热门关键词
    热门信息
    群英网络助力开启安全的云计算之旅
    立即注册,领取新人大礼包
    • 联系我们
    • 24小时售后:4006784567
    • 24小时TEL :0668-2555666
    • 售前咨询TEL:400-678-4567

    • 官方微信

      官方微信
    Copyright  ©  QY  Network  Company  Ltd. All  Rights  Reserved. 2003-2019  群英网络  版权所有   茂名市群英网络有限公司
    增值电信经营许可证 : B1.B2-20140078   粤ICP备09006778号
    免费拨打  400-678-4567
    免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
    微信公众号
    返回顶部
    返回顶部 返回顶部