geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
166 stars 21 forks source link

Go sync.Cond | Go 语言高性能编程 | 极客兔兔 #125

Open geektutu opened 3 years ago

geektutu commented 3 years ago

https://geektutu.com/post/hpg-sync-cond.html

Go 语言/golang 高性能编程,Go 语言进阶教程,Go 语言高性能编程(high performance go)。sync.Cond 是一个条件锁,也被称为条件变量,常用来一写多读(一个 goroutine 通知多个在等待的 goroutines)的场景。

2lingnil commented 3 years ago

这个时候,就需要有个全局的变量来标志第一个协程数据是否接受完毕,剩下的协程,反复简单该变量的值,直到满足要求。

反复简单 应该改为 反复检查?

Imfan commented 3 years ago

done 不是并发安全的

geektutu commented 3 years ago

@2lingnil fixed @Imfan done 前后有锁

whyming commented 3 years ago

除了Signal这个,使用场景和WaitGroup非常相似啊,而且Signal可以用chan自己实现,什么特定场景用Cond更好吗?

fufay commented 2 years ago

这个例子用单个channel做“广播”也可以,只要 close(ch)就代表发送通知了,其他的goroutine使用for select 结构来接收广播就行了。

kirkzhangtech commented 2 years ago

writer 函数里面的c.L.Lock() and c.L.Unlock()没啥作用把? 不能说明什么问题

ngyhd commented 2 years ago

使用示例中,wait既然是阻塞,第五行为什么是用for而不是if呢?

dablelv commented 2 years ago

使用示例中,“互斥锁需要保护的条件变量”,条件变量中的互斥锁并不是用来保护 done 的吧,而是用来互斥加入条件变量的等待队列的吧

dablelv commented 2 years ago

@fufay 这个例子用单个channel做“广播”也可以,只要 close(ch)就代表发送通知了,其他的goroutine使用for select 结构来接收广播就行了。

大佬,确实如此。有想到 sync.Cond 的存在的必要性了吗?

weirwei commented 2 years ago

@ngyhd 使用示例中,wait既然是阻塞,第五行为什么是用for而不是if呢?

文中有写 "对条件的检查,使用了 for !condition() 而非 if,是因为当前协程被唤醒时,条件不一定符合要求,需要再次 Wait 等待下次被唤醒。为了保险起见,使用 for 能够确保条件符合要求后,再执行后续的代码。"

maifusha commented 2 years ago

@whyming 除了Signal这个,使用场景和WaitGroup非常相似啊,而且Signal可以用chan自己实现,什么特定场景用Cond更好吗?

WaitGroup处理多通知一,Cond处理一通知多(且确保并发安全)

maifusha commented 2 years ago

@kirklonglive writer 函数里面的c.L.Lock() and c.L.Unlock()没啥作用把? 不能说明什么问题

c.L.Lock()是为了Wait函数内部并发安全的将当前协程加入Cond的通知队列,之后会解锁并挂起等待通知。 c.L.Unlock()并不是配对先前你所见的那个c.L.Lock(),而是配对的wait()内部的加锁。wait()获得通知后,并不是立即就退出函数了,wait内部接着是去抢占c.L锁的,以确保对共享资源的并发安全访问。只有当前协程抢占到c.L锁后才会从wait()退出。之后处理你的业务逻辑,最后需要你显式的c.L.Unlock()去解锁wait()退出前的锁定

maifusha commented 2 years ago

@ngyhd 使用示例中,wait既然是阻塞,第五行为什么是用for而不是if呢?

因为存在可能,某个wait协程会把资源状态改回不可用,所以其他wait协程需要二次确认。如果所有wait协程都是只读不写,我也觉着用if就好了

maifusha commented 2 years ago

@fufay 这个例子用单个channel做“广播”也可以,只要 close(ch)就代表发送通知了,其他的goroutine使用for select 结构来接收广播就行了。

Cond不仅仅是广播通知,它要做2个工作: 1 共享资源ready后,广播发出通知 2 收到通知的多个wait协程要处理并发安全的访问共享资源,可参考wait()函数内部实现,获得通知后并不是立即退出wait函数,而是去抢占c.L锁,抢到锁后wait()函数才会返回

lysShub commented 2 years ago

@dablelv

@fufay 这个例子用单个channel做“广播”也可以,只要 close(ch)就代表发送通知了,其他的goroutine使用for select 结构来接收广播就行了。

大佬,确实如此。有想到 sync.Cond 的存在的必要性了吗?

chan不能做广播,waitgroup的Done是负数时会触发panic,而cond任何时候都可以signal/broadcast。我遇到的场景就是非cond不可,你可以把它理解为触发器、心跳之类的。它是阻塞控制而不是并发安全控制

dablelv commented 2 years ago

@lysShub

@dablelv

@fufay 这个例子用单个channel做“广播”也可以,只要 close(ch)就代表发送通知了,其他的goroutine使用for select 结构来接收广播就行了。

大佬,确实如此。有想到 sync.Cond 的存在的必要性了吗?

chan不能做广播,waitgroup的Done是负数时会触发panic,而cond任何时候都可以signal/broadcast。我遇到的场景就是非cond不可,你可以把它理解为触发器、心跳之类的。它是阻塞控制而不是并发安全控制

close channel 不就可以广播了么

lysShub commented 2 years ago

那只能广播一次

---原始邮件--- 发件人: @.> 发送时间: 2022年5月8日(周日) 晚上9:25 收件人: @.>; 抄送: @.**@.>; 主题: Re: [geektutu/blog] Go sync.Cond | Go 语言高性能编程 | 极客兔兔 (#125)

@lysShub

@dablelv

@fufay 这个例子用单个channel做“广播”也可以,只要 close(ch)就代表发送通知了,其他的goroutine使用for select 结构来接收广播就行了。

大佬,确实如此。有想到 sync.Cond 的存在的必要性了吗?

chan不能做广播,waitgroup的Done是负数时会触发panic,而cond任何时候都可以signal/broadcast。我遇到的场景就是非cond不可,你可以把它理解为触发器、心跳之类的。它是阻塞控制而不是并发安全控制

close channel 不就可以广播了么

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

dablelv commented 2 years ago

@fufay 这个例子用单个channel做“广播”也可以,只要 close(ch)就代表发送通知了,其他的goroutine使用for select 结构来接收广播就行了。

我想到了一点 close channel 实现通知不如条件变量的情形。条件变量通知后可以重复使用,但是 closed 的 channel 却不能。所以还是用条件变量会更好一点。

juggggg commented 2 years ago

那么广播后,其余的协程还是要竞争锁吗?

LufeiCheng commented 1 year ago

@qq717019142 那么广播后,其余的协程还是要竞争锁吗?

从上面各位大佬的回复来看,需要竞争锁 Wait()后,首先【释放锁】,然后进入【等待通知】的状态,收到【通知】后,开始【竞争锁】,【持有锁】后执行业务逻辑代码,最后【释放锁】

lysShub commented 1 year ago

广播时,如果没有其他协程正在wait这个cond,那么这个广播相当于就被丢弃了

---原始邮件--- 发件人: @.> 发送时间: 2022年7月26日(周二) 下午4:46 收件人: @.>; 抄送: @.**@.>; 主题: Re: [geektutu/blog] Go sync.Cond | Go 语言高性能编程 | 极客兔兔 (#125)

@qq717019142 那么广播后,其余的协程还是要竞争锁吗?

从上面各位大佬的回复来看,需要竞争锁 Wait()后,首先【释放锁】,然后进入【等待通知】的状态,收到【通知】后,开始【竞争锁】,【持有锁】后执行业务逻辑代码,最后【释放锁】

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

NOS-AE commented 1 year ago

对,BroadCastSignal都只是唤醒正在Wait的协程,如果没有,那么就相当于不起作用

@lysShub 广播时,如果没有其他协程正在wait这个cond,那么这个广播相当于就被丢弃了

---原始邮件--- 发件人: @.> 发送时间: 2022年7月26日(周二) 下午4:46 收件人: @.>; 抄送: @.**@.>; 主题: Re: [geektutu/blog] Go sync.Cond | Go 语言高性能编程 | 极客兔兔 (#125)

@qq717019142 那么广播后,其余的协程还是要竞争锁吗?

从上面各位大佬的回复来看,需要竞争锁 Wait()后,首先【释放锁】,然后进入【等待通知】的状态,收到【通知】后,开始【竞争锁】,【持有锁】后执行业务逻辑代码,最后【释放锁】

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

NOS-AE commented 1 year ago

文中最后的使用实例中的write应该要添加如下代码,以免第二次调用write的时候,读者正在读

func write(name string, c *sync.Cond) {
    // 添加这几行代码将done修改为false
    c.L.Lock()
    done = false
    c.L.Unlock()

    log.Println(name, "starts writing")
    time.Sleep(time.Second)
    c.L.Lock()
    done = true
    c.L.Unlock()
    log.Println(name, "wakes all")
    c.Broadcast()
}