Goroutine仅在执行fmt.Println时有效

时间:2016-06-27 15:53:14

标签: go concurrency goroutine

出于某种原因,当我删除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)也解答了一个解决方案

4 个答案:

答案 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.CondLock函数的工作方式与我们对锁的期望相同。当我们完成检查或变异时,我们可以调用Unlock唤醒一个goroutine(或调用Signal唤醒多个),因此goroutine知道可以自由地对数据采取行动(或检查条件)。

唯一可能看似不寻常的部分是Broadcast功能。它实际上非常简单。这就像拨打Wait并立即再次呼叫Unlock一样,除非Lock不会再次尝试锁定,除非Wait(或Signal触发)在其他goroutines;比如那些正在改变数据的工人(条件)。