Goroutine 仅在执行 fmt.Println 时起作用

Goroutine only works when fmt.Println is executed

出于某种原因,当我删除 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 的回答() a solution was answered by @Kaveh Shahbazian ()

你没有给 c.Decrease() 运行 机会。 c.Block() 运行 是一个无限的 for 循环,但它从不阻塞那个 for 循环,只是在每次迭代时一遍又一遍地调用 continue。主线程无休止地以 100% 使用率自旋。

然而,当你添加一个 fmt.Print() 调用时,它会产生一个系统调用,它允许另一个 goroutine 运行。

This post 详细说明了 goroutines 是如何产生或被抢占的。但是请注意,它有点过时了,因为现在进入一个函数有一个随机的机会将该线程交给另一个 goroutine,以防止类似风格的线程泛滥。

正如其他人所指出的,Block() 永远不会屈服; goroutine 不是线程。您可以在运行时包中使用 Gosched() 来强制让步——但请注意,在 Block() 中以这种方式旋转是一个非常糟糕的想法。

有很多更好的方法来进行并发限制。一个例子见http://jmoiron.net/blog/limiting-concurrency-in-go/

你要找的东西叫做信号量。您可以使用 channels

应用此模式

http://www.golangpatterns.info/concurrency/semaphores

我们的想法是创建一个所需长度的缓冲通道。然后,您通过将一个值放入通道并在他们想要释放资源时读回该值来让调用者获取资源。这样做会在您的程序中创建适当的同步点,以便 Go 调度程序正确运行。

您现在正在做的是旋转 cpu 并阻止 Go 调度程序。这取决于你有多少个 cpu 可用,Go 的版本和 GOMAXPROCS 的值。给定正确的组合,当您无限地旋转特定线程时,可能没有另一个可用线程来为其他 goroutine 提供服务。

虽然其他答案几乎涵盖了原因(没有让 goroutine 有机会 运行)——我不确定你打算在这里实现什么——你在没有适当的情况下同时改变一个值同步。重写上述代码并考虑同步;将是:

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 是一个同步实用程序,专为您要同时检查条件是否满足的时间而设计;而其他工作人员正在改变条件的数据。

LockUnlock 函数按照我们对锁的预期工作。当我们完成检查或变异时,我们可以调用 Signal 唤醒一个 goroutine(或调用 Broadcast 唤醒多个),因此 goroutine 知道可以自由地对数据进行操作(或检查条件)。

唯一看起来不寻常的部分是 Wait 函数。其实很简单。这就像调用 Unlock 并立即再次调用 Lock - 除了 Wait 不会尝试再次锁定,除非由 Signal (或 Broadcast ) 在其他协程中;就像正在改变(条件)数据的工人。