Golang 睡眠线程而不是忙等待
Golang sleep thread instead of busy waiting
我正在编写 Leslie Lamport's Bakery algorithm 的 Go 实现,它有忙自旋等待来处理一些最大线程数。
我正在编写一个 go 函数,除非满足特殊条件,否则不应继续执行。到目前为止,我的代码如下所示:
func acquireLock() {
...
for specialConditionIsFalse {
}
...
}
是否有更有效的方法来停止处理此线程?
从当前版本开始,goroutines 是不可抢占的。这意味着如果你有一个带有紧密循环的 goroutine,那个 goroutine 将不会让出它正在 运行 连接到其他 goroutines 的线程。有时这可能意味着没有其他 goroutine 会 运行.
不要像这样忙着等待,而是使用频道:
<-specialCondition
// Do stuff
特殊情况发生时关闭
您也可以尝试使用 sync.Cond
,但是您可以使用通道来完成条件变量所做的一切。
这里有几点值得注意:
goroutines 不是线程。没有"goroutine number",系统中goroutine的数量也没有固定的上限。1 可以修改Bakery算法来处理动态创建的线程(使用一个列表或地图,如维基百科页面上的 Java 示例),但强烈要求每个 "thread" 有一个唯一的 ID,这使得这对于 Go 来说通常不是一个好主意。 (您可以使用实现类似线程行为(包括线程 ID)的包来解决这个问题。)
如维基百科页面所述:
Lamport's bakery algorithm assumes a sequential consistency memory model. Few, if any, languages or multi-core processors implement such a memory model. Therefore correct implementation of the algorithm typically requires inserting fences to inhibit reordering.
这意味着您将需要使用 sync/atomic
包,这违背了您编写自己的锁定的目的。
有了这两个巨大的警告,您可以在调用 POSIX 样式 yield()
函数的地方调用 runtime.Gosched()
,或者您可以使用一个通道来表示某人有 "left the bakery" 因此轮到下一个用户了。但是通道本身可以完成您需要的所有互斥。一个简化的 Go 特定的非 Lamport 面包店算法是微不足道的(但以下所有内容都未经测试):
var takeANumber chan int64
var currentlyServing int64
init() {
takeANumber = make(chan int64)
go giveNumbers()
}
// giveNumbers hands out ever-increasing ticket numbers
func giveNumbers() {
for int64 i := 0;; i++ {
takeANumber <- i
}
}
// WaitTurn gets a ticket, then waits until it is our turn. You can
// call this "Lock" if you like.
func WaitTurn() int64 {
ticket := <-takeANumber
for atomic.LoadInt64(¤tlyServing) < ticket {
runtime.Gosched()
}
return ticket
}
// ExitBakery relinquishes our ticket, allowing the next user to proceed.
func ExitBakery(ticket int64) {
atomic.StoreInt64(¤tlyServing, ticket + 1)
}
修改它以使用两个通道,使 WaitTurn
功能更有效,留作练习。 (当然,除了作为练习之外,没有理由一开始就使用这些代码。)
1您可以设置运行时限制,但如果您调用任何阻塞系统调用,系统无论如何都会产生额外的 goroutine。阻塞的系统调用集以及它们何时被调用取决于运行时,因此您无法真正控制它,至少在没有编写特定于平台的代码的情况下是这样。
我正在编写 Leslie Lamport's Bakery algorithm 的 Go 实现,它有忙自旋等待来处理一些最大线程数。
我正在编写一个 go 函数,除非满足特殊条件,否则不应继续执行。到目前为止,我的代码如下所示:
func acquireLock() {
...
for specialConditionIsFalse {
}
...
}
是否有更有效的方法来停止处理此线程?
从当前版本开始,goroutines 是不可抢占的。这意味着如果你有一个带有紧密循环的 goroutine,那个 goroutine 将不会让出它正在 运行 连接到其他 goroutines 的线程。有时这可能意味着没有其他 goroutine 会 运行.
不要像这样忙着等待,而是使用频道:
<-specialCondition
// Do stuff
特殊情况发生时关闭
您也可以尝试使用 sync.Cond
,但是您可以使用通道来完成条件变量所做的一切。
这里有几点值得注意:
goroutines 不是线程。没有"goroutine number",系统中goroutine的数量也没有固定的上限。1 可以修改Bakery算法来处理动态创建的线程(使用一个列表或地图,如维基百科页面上的 Java 示例),但强烈要求每个 "thread" 有一个唯一的 ID,这使得这对于 Go 来说通常不是一个好主意。 (您可以使用实现类似线程行为(包括线程 ID)的包来解决这个问题。)
如维基百科页面所述:
Lamport's bakery algorithm assumes a sequential consistency memory model. Few, if any, languages or multi-core processors implement such a memory model. Therefore correct implementation of the algorithm typically requires inserting fences to inhibit reordering.
这意味着您将需要使用
sync/atomic
包,这违背了您编写自己的锁定的目的。
有了这两个巨大的警告,您可以在调用 POSIX 样式 yield()
函数的地方调用 runtime.Gosched()
,或者您可以使用一个通道来表示某人有 "left the bakery" 因此轮到下一个用户了。但是通道本身可以完成您需要的所有互斥。一个简化的 Go 特定的非 Lamport 面包店算法是微不足道的(但以下所有内容都未经测试):
var takeANumber chan int64
var currentlyServing int64
init() {
takeANumber = make(chan int64)
go giveNumbers()
}
// giveNumbers hands out ever-increasing ticket numbers
func giveNumbers() {
for int64 i := 0;; i++ {
takeANumber <- i
}
}
// WaitTurn gets a ticket, then waits until it is our turn. You can
// call this "Lock" if you like.
func WaitTurn() int64 {
ticket := <-takeANumber
for atomic.LoadInt64(¤tlyServing) < ticket {
runtime.Gosched()
}
return ticket
}
// ExitBakery relinquishes our ticket, allowing the next user to proceed.
func ExitBakery(ticket int64) {
atomic.StoreInt64(¤tlyServing, ticket + 1)
}
修改它以使用两个通道,使 WaitTurn
功能更有效,留作练习。 (当然,除了作为练习之外,没有理由一开始就使用这些代码。)
1您可以设置运行时限制,但如果您调用任何阻塞系统调用,系统无论如何都会产生额外的 goroutine。阻塞的系统调用集以及它们何时被调用取决于运行时,因此您无法真正控制它,至少在没有编写特定于平台的代码的情况下是这样。