大神解释下:不是说锁不可重入么

wujiangwei · 2019-01-30 14:03:57 · 1604 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2019-01-30 14:03:57 的主题,其中的信息可能已经有所发展或是发生改变。

代码是这样的

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 这处锁,

为什么没有出现死锁, 不可重入 又该怎么理解呢?


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1604 次点击  
加入收藏 微博
13 回复  |  直到 2019-02-11 17:34:01
jarlyyn
jarlyyn · #1 · 6年之前

因为3-7处并没有执行6……

你可以理解为6是告诉系统将要执行removeFromQueue

wujiangwei
wujiangwei · #2 · 6年之前
jarlyynjarlyyn #1 回复

因为3-7处并没有执行6…… 你可以理解为6是告诉系统将要执行removeFromQueue

我们抛开 6 的时间延迟,程序仍然能正常执行,正如你所说的将要执行 , 那我们起了10个goroutine 会不会有某一个 goroutine与 maingoroutine 同时执行,如果有,那么这个问题可能依然存在,或者说我理解的方向有误,那么正确的理解应该是什么呢

wujiangwei
wujiangwei · #3 · 6年之前

我们去掉 6 中函数前面的 go 使它变成普通函数,立马就出现了死锁, 然后我们加上了 go ,死锁消失,怎么理解goroutine的可重入

katsu
katsu · #4 · 6年之前

removeFromQueue()开始会sleep一段时间,之前看过篇文章执行time.sleep的时候会让出执行权,所以执行8的时候一般主函数的7已经执行过了,如果是多个P的话应该是启动协程后会瞬间就执行完7的Unlock了

jarlyyn
jarlyyn · #5 · 6年之前
wujiangweiwujiangwei #2 回复

#1楼 @jarlyyn 我们抛开 6 的时间延迟,程序仍然能正常执行,正如你所说的`将要执行` , 那我们起了10个goroutine 会不会有某一个 goroutine与 maingoroutine 同时执行,如果有,那么这个问题可能依然存在,或者说我理解的方向有误,那么正确的理解应该是什么呢

……

看来你没写过js,不了解runtime.nextTick之类的概念。

首先,我只是告诉你为什么你这个代码没有锁死,不代表他不能锁死。请理解下我的意思。

其次我说的将要执行是指go这个命令本身做的事情,和你是否加入time.sleep没关系。

最后,你这个代码,就算在主循环中加入time.sleep,也只会锁住,不会锁死…………

sdghchj
sdghchj · #6 · 6年之前

多线程锁机制没搞清楚? 虽然我现在也不清楚协程是怎么实现的,和线程有什么区别,但是也知道: 不同线程要争抢同一资源的时候,需要对资源加锁。 线程1锁住后,线程2会在lock处会阻塞,直到线程1释放锁之后才被唤醒继续执行。

go是开启一个新的协程去执行后面的函数代码,协程1里是锁住了,但是并不会影响协程2里的lock,只是协程2会在lock处阻塞等待。

HN-JIE
HN-JIE · #7 · 6年之前

楼上说的也是我想说的

HN-JIE
HN-JIE · #8 · 6年之前

func main() { c := sync.NewCond(&sync.Mutex{}) //1 queue := make([]interface{}, 0, 10) //2 c.L.Lock() //3 c.L.Unlock() //4

}

可重入和不可重入是对同一个协程(或者线程)来说的,如果同一个协程(或者线程)能多次获取同一个锁,那就是可重入的,可重入的在4处就不会阻塞;对于不可重入的,4处就会阻塞

dosecin
dosecin · #9 · 6年之前

看sync.Cond的源码

func (c *Cond) Wait() {
    c.checker.check()
    t := runtime_notifyListAdd(&c.notify)
    c.L.Unlock()
    runtime_notifyListWait(&c.notify, t)
    c.L.Lock()
}

进入c.Wait()后会解锁

DyingLight123
DyingLight123 · #10 · 6年之前

楼上给的代码很容易解释通,c.L.Unlock()解锁之后,要等待Signal()发送通知,在执行11之后,c.wait()会再次上锁,继续执行。至于go removeFromQueue(1 * time.Second)是并发执行,不同的进程,需要在主进程解锁之后才会找空挡执行。

orestonce
orestonce · #11 · 6年之前
dosecindosecin #9 回复

看sync.Cond的源码 ``` func (c *Cond) Wait() { c.checker.check() t := runtime_notifyListAdd(&c.notify) c.L.Unlock() runtime_notifyListWait(&c.notify, t) c.L.Lock() } ``` 进入c.Wait()后会解锁

+1

yuyang
yuyang · #12 · 6年之前

关于不可重入最典型的例子,以前在c 语言编程时碰到过 =》“信号处理函数里调用了malloc ”。死锁的原因为:在其他地方调用malloc 时 lock 后还未释放锁, cpu中断立即响应 信号处理函数 又调用了malloc 形成了重入死锁(malloc是线程安全的)

wujiangwei
wujiangwei · #13 · 6年之前

8楼说到点子上来了

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传