Go select 死锁的一个细节

目录

下面对是一个 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 死锁的一个细节的文章就介绍到这了,更多相关Go select 死锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go语言使用select{}阻塞main函数介绍

    很多时候我们需要让main函数不退出,让它在后台一直执行,例如: func main() { for i := 0; i < 20; i++ { //启动20个协程处理消息队列中的消息 c := consumer.New() go c.Start() } select {} // 阻塞 } 可能大多数人想到阻塞的方法是用channel,当然都是可以的,不过用select{}更加简洁 :) 补充:由浅入深聊聊Golang中select的实现机制 正文 话说今天在玩select的时候发现一个问题,是

  • golang coroutine 的等待与死锁用法

    直接上代码: 1. 第一种情况 如果没有select{}, main 主线程不会等待coroutine运行,导致coroutine得不到机会运行. You are requesting eventual scheduling (using the two go statements) of two goroutines and then you exit main without giving the scheduler a chance to do anything. 有了select, 程序

  • 详解Golang并发操作中常见的死锁情形

    目录 第一种情形:无缓存能力的管道,自己写完自己读 第二种情形:协程来晚了 第三种情形:管道读写时,相互要求对方先读/写 第四种情形:读写锁相互阻塞,形成隐形死锁 什么是死锁,在Go的协程里面死锁通常就是永久阻塞了,你拿着我的东西,要我先给你然后再给我,我拿着你的东西又让你先给我,不然就不给你.我俩都这么想,这事就解决不了了. 第一种情形:无缓存能力的管道,自己写完自己读 先上代码: func main() { ch := make(chan int, 0) ​ ch <- 666 x := <

  • Go 语言中的死锁问题解决

    目录 死锁 如何避免死锁 死锁代码 死锁 死锁的4个条件 不可剥夺 线程已经获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完后自己释放. 请求保持 线程 T1 保持了一个资源 R1 占用,但是又提出另外一个资源 R2 请求,此时,资源 R2 被线程 T2 占用,于是 T1 线程必须等待,但又对自己保持的 R1 资源不释放. 循环等待 死锁发生时,必然存在一个 "进程-资源环形链",例如 进程p0 等待 p1 占用资源,p1 等待 p2 占用的资源, p2 等待 p0 占用的

  • golang中的select关键字用法总结

    1.官方解释 一个select语句用来选择哪个case中的发送或接收操作可以被立即执行.它类似于switch语句,但是它的case涉及到channel有关的I/O操作.即select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作. 2.要点 如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进

  • go select编译期的优化处理逻辑使用场景分析

    前言 select作为Go chan通信的重要监听工具,有着很广泛的使用场景.select的使用主要是搭配通信case使用,表面上看,只是简单的select及case搭配,实际上根据case的数量及类型,在编译时select会进行优化处理,根据不同的情况调用不同的底层逻辑. select的编译处理 select编译时的核心处理逻辑如下: func walkselectcases(cases *Nodes) []*Node { ncas := cases.Len() sellineno := li

  • Go语言死锁与goroutine泄露问题的解决

    目录 什么时候会导致死锁 发送单个值时的死锁 多个值发送的死锁 解决多值发送死锁 应该先发送还是先接收 goroutine 泄漏 如何发现泄露 小结 什么时候会导致死锁 在计算机组成原理里说过 死锁有三个必要条件他们分别是 循环等待.资源共享.非抢占式,在并发中出现通道死锁只有两种情况: 数据要发送,但是没有人接收 数据要接收,但是没有人发送 发送单个值时的死锁 牢记这两点问题就很清晰了,复习下之前的例子,会死锁 a := make(chan int) a <- 1 //将数据写入channel

  • Django实现jquery select2带搜索的下拉框

    最近在开发一个web应用中需要用到带搜索功能下拉框,曾经尝试网上的django 包, django-select2-forms, 这是款功能强大的packets, 可惜不支持我的开发环境centos+python3.7+django 2.0.2, 只好下载原生jquery select2 ,通过参考官方文档,多次测试最终实现了简单的带搜索功能下拉框,它根据在前端搜索框输入的字符,ajax调用后台的试图函数查询数据库,然后将查询结果返回给前端生成下拉框列表. 1,前端代码 <script src=

  • matplotlib之多边形选区(PolygonSelector)的使用

    多边形选区概述 多边形选区是一种常见的对象选择方式,在一个子图中,单击鼠标左键即构建一个多边形的端点,最后一个端点与第一个端点重合即完成多边形选区,选区即为多个端点构成的多边形.在matplotlib中的多边形选区属于部件(widgets),matplotlib中的部件都是中性(neutral )的,即与具体后端实现无关. 多边形选区具体实现定义为matplotlib.widgets.PolygonSelector类,继承关系为:Widget->AxesWidget->_SelectorWid

  • Go select 死锁的一个细节

    目录 下面对是一个 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") } }()

  • 查询Sqlserver数据库死锁的一个存储过程分享

    使用sqlserver作为数据库的应用系统,都避免不了有时候会产生死锁, 死锁出现以后,维护人员或者开发人员大多只会通过sp_who来查找死锁的进程,然后用sp_kill杀掉.利用sp_who_lock这个存储过程,可以很方便的知道哪个进程出现了死锁,出现死锁的问题在哪里. 创建sp_who_lock存储过程 CREATE procedure sp_who_lock as begin declare @spid int declare @blk int declare @count int de

  • JavaScript中switch判断容易犯错的一个细节

    switch语句与if语句的关系最为密切,也是其它编程语言中普遍使用的一种流程控制语句,但switch的匹配是全等模式,如果不注意这个细节则写程序时往往会出错. 代码: var n = '5'; switch(n){     case 5:         alert('执行case分支');         break;     default:         alert('执行default分支'); } 结果: 可能很多人会误以为以上程序会走case分支,结果却走了default分支.难

  • java中Integer包装类装箱的一个细节详解

    前言 java有八个基本数据类型,每个都有对应的一个包装类,比如int对应的Integer. Integer 是int的包装类型,数据类型是类,初值为null,从jdk1.5开始,java引入了自动拆装箱,可以直接进行形如Integer i = 20形式的赋值,编译器会自动将其转换为Integer i = Integer.valueOf(20)进行装箱,拆箱则是将int j = i的形式转换成了int j = i.intValue() . 装箱有个细节,如果不注意很容易出错,来看一下: Inte

  • jquery select选中的一个小问题

    jquery使select选中: 用$("#select").attr('value','2');时firefox可以,但ie6不可以 改用$("#select").val('2'),两者都可以了,而且更简洁 自己的理解:value不是select的正规属性,这个value存在于<option>里,所以第一句在兼容上有点问题

  • Jquery操作Select 简单方便 一个js插件搞定

    这里是js的代码: 复制代码 代码如下: jQuery.fn.size = function() { return jQuery(this).get(0).options.length; } //获得选中项的索引 jQuery.fn.getSelectedIndex = function() { return jQuery(this).get(0).selectedIndex; } //获得当前选中项的文本 jQuery.fn.getSelectedText = function() { if(

  • Go语言开发必知的一个内存模型细节

    目录 引言 内存模型定义是什么 happens-before 是什么 A 不一定 happens-before B Go 语言中的 happens-before 定义 Go Channel 实例 例子 1 例子 2 例子 3 例子 4 总结 引言 在日常工作中,如果我们能够了解 Go 语言内存模型,那会带来非常大的作用.这样在看一些极端情况,又或是变态面试题的时候,就能够明白程序运行表现下的很多根本原因了. 当然,靠一篇普通文章讲完 Go 内存模型,不可能.因此今天这篇文章,把重点划在给大家讲解

  • 详解通过SQL进行分布式死锁的检测与消除

    概述 分布式数仓应用场景中,我们经常遇到数据库系统 hang 住的问题,所谓 hang 是指虽然数据库系统还在运行,但部分或全部业务无法正常执行.hang 问题的原因有很多,其中以分布式死锁最为常见,本次主要分享在碰到死锁时,如何快速地解决死锁问题. GaussDB(DWS) 作为分布式数仓,通过锁机制来实行并发控制,因此也存在产生分布式死锁的可能.虽然分布式死锁无法避免,但幸运的是其提供了多种系统视图,能够保证在分布式死锁发生之后,快速地对死锁进行定位. 本文主要介绍了在 GaussDB(DW

  • 关于go语言载入json可能遇到的一个坑

    问题简介 Go语言是一个简单却蕴含深意的语言,大家都知道go语言标准库汇总内置了对 json 文件的处理,非常方便,最近在写一个应用的时候,需要从 json 文件中载入 配置,由于是 go 新手,忽略一个细节,导致载入内容始终为空,下面话不多说,需要的朋友们一起来看看详细的介绍: 代码演示 代码是最好的说明载体 package config type config struct{ a string `json:"a"` b string `json:"a"` } f

随机推荐