英文:
Recursive critical sections in Go
问题
我理解到在Go语言中没有对递归互斥锁提供支持(而且很多人认为这些锁是危险的),而且通道是实现复杂并发模式的首选方式。
然而,我无法找到任何明智的方法来实现一个非常常见的并发模式 - 可重入或递归的临界区。
大致上:goroutine A 和 B 将竞争一个关键区域的锁(比如说需要原子修改的结构体中的某个状态)。假设 A 获得了锁。然而,A 将会递归,可能需要多次进入关键区域。当它像进入时一样退出关键区域后,goroutine B 将获得锁,依此类推。
我想使用通道来实现这个(或者以其他任何方式在Go中实现),而不必通过整个函数调用树传递一些字符串或令牌(没有“goroutine id”可用),也不必使用runtime
包进行混乱/昂贵的堆栈检查。
如何实现这个呢?
英文:
I understand that there is no support for recursive mutexes in Go (and that a lot of folks consider these dangerous), and that channels are the preferred way to implement complex concurrency patterns.
However, I cannot figure out any sensible way to implement a very common concurrency pattern - that of the re-entrant or recursive critical section.
Roughly: goroutines A and B will compete for a lock over a critical section (say some state in a struct needs to be atomically modified). Let's say A receives the lock. However, A will recurse, possibly needing to enter the critical section many times. When it has exited the critical section as it has entered it, goroutine B will get the lock, etc.
I want to implement this with channels (or in any way possible in Go otherwise), without having to pass some string or token back and forth through the whole call tree of functions that might pass through the critical section (there is no "goroutine id" available)., and without having messy/expensive stack introspection necessary using runtime
package.
How can this be done?
答案1
得分: 1
你好,以下是翻译好的内容:
假设,你上面的示例代码如下所示:
package main
import "fmt"
type Foo struct {
// 这里必须是可重入的互斥锁
Value int
}
var F Foo
func A() {
F.Value += 1
if F.Value < 10 {
A()
}
}
func B() {
F.Value += 5
if F.Value < 20 {
A()
}
}
func main() {
F = Foo{
Value: 0,
}
A()
B()
fmt.Println("F is", F.Value)
}
然后,使用通道进行实现应遵循简单原则-只有一个地方读取或写入F.Value,由select
语句包裹。类似于以下代码:
package main
import "fmt"
type Foo struct {
Value int
}
var (
F Foo
ch = make(chan int)
)
func A() {
val := <-ch
ch <- val + 1
if val < 10 {
A()
}
}
func B() {
val := <-ch
ch <- val + 5
if val < 20 {
A()
}
}
func main() {
F = Foo{
Value: 0,
}
go func() {
for {
select {
case val := <-ch:
F.Value = val
case ch <- F.Value:
}
}
}()
A()
B()
fmt.Println("F is", F.Value)
}
在这里,我们使用双向缓冲通道来获取/设置F.Value。一个读取者,一个写入者,select
语句处理所有访问的魔法。
你可能还对golang-nuts上关于可重入互斥锁的相关主题感兴趣:https://groups.google.com/forum/#!topic/golang-nuts/vN5ncBdtkcA 这里有很好的解释,为什么在Go中可重入互斥锁没有用(这不是危险的问题)。
英文:
Say, your example above looks like:
package main
import "fmt"
type Foo struct {
// here must be reentrant mutex
Value int
}
var F Foo
func A() {
F.Value += 1
if F.Value < 10 {
A()
}
}
func B() {
F.Value += 5
if F.Value < 20 {
A()
}
}
func main() {
F = Foo{
Value: 0,
}
A()
B()
fmt.Println("F is", F.Value)
}
http://play.golang.org/p/STnaLUCaqP
Then implementation in channels should follow simple principle - only one place where F.Value is read or written, wrapped by select statement. Something like this:
package main
import "fmt"
type Foo struct {
Value int
}
var (
F Foo
ch = make(chan int)
)
func A() {
val := <-ch
ch <- val+1
if val < 10 {
A()
}
}
func B() {
val := <-ch
ch <- val+5
if val < 20 {
A()
}
}
func main() {
F = Foo{
Value: 0,
}
go func() {
for {
select {
case val := <-ch:
F.Value = val
case ch <- F.Value:
}
}
}()
A()
B()
fmt.Println("F is", F.Value)
}
http://play.golang.org/p/e5M4vTeet2
Here we use bidirectional buffered channel for getting/setting F.Value. One reader, one writer, select does all the magic to handling access.
You may also be interested in relevant topic in golang-nuts on the reentrant mutexes: https://groups.google.com/forum/#!topic/golang-nuts/vN5ncBdtkcA There are good explanation why reentrant mutexes are not useful in Go (and it's not the question of danger).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论