- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Go select 死锁的一个细节由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
下面对是一个 select 死锁的问题 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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) 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
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 函数中:
1
2
3
4
|
select
{
case
ch <- <-input1:
case
ch <- <-input2:
}
|
如果改为这样就一切正常:
1
2
3
4
5
6
|
select
{
case
t := <-input1:
ch <- t
case
t := <-input2:
ch <- t
}
|
结合这个更复杂的例子分析 Go 语言规范中的那句话.
对于 select 语句,在进入该语句时,会按源码的顺序对每一个 case 子句进行求值:这个求值只针对发送或接收操作的额外表达式.
比如:
1
2
3
4
5
6
7
8
9
10
|
// 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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 最终选择了哪个 case, getVal() 都会按照源码顺序执行: getVal(1) 和 getVal(2) ,也就是它们必然先输出:
1
2
|
getVal, i=
1
getVal, i=
2
|
你可以仔细琢磨一下.
现在回到 StackOverflow 上的那个问题.
每次进入以下 select 语句时:
1
2
3
4
|
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 次后,报死锁.
虽然这是一个小细节,但实际开发中还是有可能出现的。比如文章提到的例子写法:
1
2
3
4
5
6
7
8
9
10
|
//
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 的时间参数越大,泄露会越厉害),你能解释原因吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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 死锁内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://www.tuicool.com/articles/NZfQFfu 。
最后此篇关于Go select 死锁的一个细节的文章就讲到这里了,如果你想了解更多关于Go select 死锁的一个细节的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
一、公平锁和非公平锁 1.1、公平锁和非公平锁的概述 公平锁:指多个线程按照申请锁的顺序来获取锁。 非公平锁:指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁
阅读目录 1、简介 2、分类 3、全局锁 4、表级锁 5、表锁 6、元数据锁
因此,在我编写的程序中,我有三个函数,为了简单起见,我们将它们称为 A、B 和 C。每个函数都需要访问资源X才能工作。 限制是A和B不允许同时运行并且必须适当同步。但是,C 可以与 A 或 B 同时运
我听说过这些与并发编程相关的词,但是锁、互斥量和信号量之间有什么区别? 最佳答案 锁只允许一个线程进入被锁定的部分,并且该锁不与任何其他进程共享。 互斥锁与锁相同,但它可以是系统范围的(由多个进程共享
这个问题已经有答案了: What is an efficient way to implement a singleton pattern in Java? [closed] (29 个回答) 已关闭
这个问题已经有答案了: What is an efficient way to implement a singleton pattern in Java? [closed] (29 个回答) 已关闭
我对标题中的主题有几个问题。首先,假设我们使用 JDBC,并且有 2 个事务 T1 和 T2。在 T1 中,我们在一个特定的行上执行 select 语句。然后我们对该行执行更新。在事务 T2 中,我们
我希望我的函数只运行一次。这意味着如果多个线程同时调用它,该函数将阻塞所有线程,只允许它运行。 最佳答案 听起来您希望存储过程进行同步。为什么不直接将同步放在应用程序本身中。 pthread_mute
if (runInDemoMode) { lock (this) { //Initalization of tables dCreator.create
我相信无论使用什么语言都可以考虑我的问题,但是为了有一些“ anchor ”,我将使用 Java 语言来描述它。 让我们考虑以下场景:我有一个扩展 Thread 的类 PickyHost 及其实例 p
我知道异步不是并行的,但我现在遇到了一个非常有趣的情况。 async function magic(){ /* some processing here */ await async () =
我们正在使用 Scala、Play 框架和 MongoDB(以 ReactiveMongo 作为我们的驱动程序)构建一个网络应用程序。应用程序架构是端到端的非阻塞。 在我们代码的某些部分,我们需要访问
我需要一个简单的锁,JavaME 超时(concurrent.lock 的反向移植需要完整的 Java 1.3)。 如果其他人已经为 JavaME 发布了经过测试的锁定代码,我宁愿使用它。 锁定是出了
根据 boost : To access the object, a weak_ptr can be converted to a shared_ptr using the shared_ptr co
关于 Mutex 和 Critical 部分之间的区别存在一个问题,但它也不处理 Locks。 所以我想知道临界区是否可以用于进程之间的线程同步。 还有信号状态和非信号状态的含义 最佳答案 在 Win
锁 最为常见的应用就是 高并发的情况下,库存的控制。本次只做简单的单机锁介绍。 直接看代码: 每请求一次库存-1. 假如库存1000,在1000个人请求之后,库存将变为0。
线程和进程 1、线程共享创建它的进程的地址空间,进程有自己的地址空间 2、线程可以访问进程所有的数据,线程可以相互访问 3、线程之间的数据是独立的 4、子进程复制线程的数据 5、子进程启动
**摘要:**细心的你也一定关注到,有的网址是https开头的,有的是http。https开头的网站前面,会有一把小锁。这是为什么呢? 本文分享自华为云社区《还不知道SSL证书已经是刚需了?赶快来了解
试图在 C 中实现一个非常简单的互斥锁(锁)我有点困惑。我知道互斥锁类似于二进制信号量,除了互斥锁还强制执行释放锁的线程的约束,必须是最近获得它的同一线程。我对如何跟踪所有权感到困惑? 这是我到目前为
在阅读了很多与上述主题相关的文章和答案之后,我仍然想知道 SQL Server 数据库引擎在以下示例中是如何工作的: 假设我们有一个名为 t3 的表: create table t3 (a int ,
我是一名优秀的程序员,十分优秀!