代码是这样的
package main
import (
"fmt"
"sync"
"time"
)
func main() {
c := sync.NewCond(&sync.Mutex{}) //1
queue := make([]interface{}, 0, 10) //2
removeFromQueue := func(delay time.Duration) {
time.Sleep(delay)
c.L.Lock() //8
queue = queue[1:] //9
fmt.Println("Removed from queue")
c.L.Unlock() //10
c.Signal() //11
}
for i := 0; i < 10; i++ {
c.L.Lock() //3
for len(queue) == 2 { //4
c.Wait() //5
}
fmt.Println("Adding to queue")
queue = append(queue, struct{}{})
go removeFromQueue(1 * time.Second) //6
c.L.Unlock() //7
}
}
代码来源摘自 :https://www.kancloud.cn/mutouzhang/go/596828
第
3
处 和 第8
处 都加了锁,该锁都来自条件锁 ‘c’ 中的L
,且L
是互斥锁Mutex
,从代码执行的顺序来看, 从
3
---7
之间 执行了6
,未释放锁之前
6
中 又加了8
这处锁,为什么没有出现死锁, 不可重入 又该怎么理解呢?
有疑问加站长微信联系(非本文作者)

因为3-7处并没有执行6……
你可以理解为6是告诉系统将要执行removeFromQueue
我们抛开 6 的时间延迟,程序仍然能正常执行,正如你所说的
将要执行
, 那我们起了10个goroutine 会不会有某一个 goroutine与 maingoroutine 同时执行,如果有,那么这个问题可能依然存在,或者说我理解的方向有误,那么正确的理解应该是什么呢我们去掉 6 中函数前面的
go
使它变成普通函数,立马就出现了死锁, 然后我们加上了go
,死锁消失,怎么理解goroutine的可重入removeFromQueue()开始会sleep一段时间,之前看过篇文章执行time.sleep的时候会让出执行权,所以执行8的时候一般主函数的7已经执行过了,如果是多个P的话应该是启动协程后会瞬间就执行完7的Unlock了
……
看来你没写过js,不了解runtime.nextTick之类的概念。
首先,我只是告诉你为什么你这个代码没有锁死,不代表他不能锁死。请理解下我的意思。
其次我说的将要执行是指go这个命令本身做的事情,和你是否加入time.sleep没关系。
最后,你这个代码,就算在主循环中加入time.sleep,也只会锁住,不会锁死…………
多线程锁机制没搞清楚? 虽然我现在也不清楚协程是怎么实现的,和线程有什么区别,但是也知道: 不同线程要争抢同一资源的时候,需要对资源加锁。 线程1锁住后,线程2会在lock处会阻塞,直到线程1释放锁之后才被唤醒继续执行。
go是开启一个新的协程去执行后面的函数代码,协程1里是锁住了,但是并不会影响协程2里的lock,只是协程2会在lock处阻塞等待。
楼上说的也是我想说的
func main() { c := sync.NewCond(&sync.Mutex{}) //1 queue := make([]interface{}, 0, 10) //2 c.L.Lock() //3 c.L.Unlock() //4
}
可重入和不可重入是对同一个协程(或者线程)来说的,如果同一个协程(或者线程)能多次获取同一个锁,那就是可重入的,可重入的在4处就不会阻塞;对于不可重入的,4处就会阻塞
看sync.Cond的源码
进入c.Wait()后会解锁
楼上给的代码很容易解释通,c.L.Unlock()解锁之后,要等待Signal()发送通知,在执行11之后,c.wait()会再次上锁,继续执行。至于go removeFromQueue(1 * time.Second)是并发执行,不同的进程,需要在主进程解锁之后才会找空挡执行。
+1
关于不可重入最典型的例子,以前在c 语言编程时碰到过 =》“信号处理函数里调用了malloc ”。死锁的原因为:在其他地方调用malloc 时 lock 后还未释放锁, cpu中断立即响应 信号处理函数 又调用了malloc 形成了重入死锁(malloc是线程安全的)
8楼说到点子上来了