出于某种原因,当我删除fmt.Println
时,代码就会被阻止。
我不知道为什么会这样。我想要做的就是实现一个简单的并发限制器......
我从未经历过如此奇怪的事情。它就像fmt
刷新变量或其他东西并使其有效。
另外,当我使用常规函数而不是goroutine时,它也能正常工作。
这是以下代码 -
package main
import "fmt"
type ConcurrencyLimit struct {
active int
Limit int
}
func (c *ConcurrencyLimit) Block() {
for {
fmt.Println(c.active, c.Limit)
// If should block
if c.active == c.Limit {
continue
}
c.active++
break
}
}
func (c *ConcurrencyLimit) Decrease() int {
fmt.Println("decrease")
if c.active > 0 {
c.active--
}
return c.active
}
func main() {
c := ConcurrencyLimit{Limit: 1}
c.Block()
go func() {
c.Decrease()
}()
c.Block()
}
澄清:即使我接受了@kaedys的回答(here),@ Kaveh Shahbazian(here)也解答了一个解决方案
答案 0 :(得分:7)
你没有给c.Decrease()
一个机会跑。 c.Block()
运行一个无限for循环,但它永远不会阻塞for循环,只是在每次迭代时反复调用continue
。主线程无休止地使用100%。
但是,当您添加fmt.Print()
调用时,会发出一个系统调用,允许其他goroutine运行。
This post详细说明了goroutines如何产生或被抢先一步。但请注意,它稍微过时了,因为现在输入一个函数有一个随机的机会将该线程传递给另一个goroutine,以防止类似样式泛滥的线程。
答案 1 :(得分:1)
正如其他人所指出的,Block()永远不会屈服; goroutine不是一个主题。您可以在运行时包中使用Gosched()来强制产生 - 但请注意,在Block()中以这种方式旋转是一个非常糟糕的主意。
有更好的方法来进行并发限制。有关示例,请参阅http://jmoiron.net/blog/limiting-concurrency-in-go/
答案 2 :(得分:1)
您正在寻找的是一种信号量。您可以使用频道
应用此模式http://www.golangpatterns.info/concurrency/semaphores
这个想法是你创建一个所需长度的缓冲通道。然后,通过将值放入通道并在想要释放资源时将其读回来,使调用者获取资源。这样做会在程序中创建正确的同步点,以便Go调度程序正确运行。
您现在正在做的是旋转cpu并阻止Go调度程序。这取决于你有多少cpu,Go的版本和GOMAXPROCS的值。给定正确的组合,当你无限地旋转那个特定的线程时,可能没有另一个可用的线程来服务其他goroutine。
答案 3 :(得分:1)
虽然其他答案几乎涵盖了原因(没有给goroutine运行的机会) - 而且我不确定你打算在这里实现什么 - 你在没有适当同步的情况下同时改变一个值。考虑同步重写上述代码;将是:
internal
type ConcurrencyLimit struct {
active int
Limit int
cond *sync.Cond
}
func (c *ConcurrencyLimit) Block() {
c.cond.L.Lock()
for c.active == c.Limit {
c.cond.Wait()
}
c.active++
c.cond.L.Unlock()
c.cond.Signal()
}
func (c *ConcurrencyLimit) Decrease() int {
defer c.cond.Signal()
c.cond.L.Lock()
defer c.cond.L.Unlock()
fmt.Println("decrease")
if c.active > 0 {
c.active--
}
return c.active
}
func main() {
c := ConcurrencyLimit{
Limit: 1,
cond: &sync.Cond{L: &sync.Mutex{}},
}
c.Block()
go func() {
c.Decrease()
}()
c.Block()
fmt.Println(c.active, c.Limit)
}
是一个同步实用程序,设计用于您要同时检查条件是否满足的时间;而其他工人正在改变这种情况的数据。
sync.Cond
和Lock
函数的工作方式与我们对锁的期望相同。当我们完成检查或变异时,我们可以调用Unlock
唤醒一个goroutine(或调用Signal
唤醒多个),因此goroutine知道可以自由地对数据采取行动(或检查条件)。
唯一可能看似不寻常的部分是Broadcast
功能。它实际上非常简单。这就像拨打Wait
并立即再次呼叫Unlock
一样,除非Lock
不会再次尝试锁定,除非Wait
(或Signal
触发)在其他goroutines;比如那些正在改变数据的工人(条件)。