Goroutine仅在执行fmt.Println时有效

Goroutine仅在执行fmt.Println时有效,go,concurrency,goroutine,Go,Concurrency,Goroutine,出于某种原因,当我删除fmt.Printlns时,代码被阻塞了。 我不知道为什么会这样。我只想实现一个简单的并发限制器 我从来没有经历过这么奇怪的事情。这就像fmt刷新变量或其他东西并使其工作一样 此外,当我使用常规函数而不是goroutine时,它也可以工作 以下是代码- package main import "fmt" type ConcurrencyLimit struct { active int Limit int } func (c *Concurrency

出于某种原因,当我删除fmt.Printlns时,代码被阻塞了。 我不知道为什么会这样。我只想实现一个简单的并发限制器

我从来没有经历过这么奇怪的事情。这就像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的回答,但@Kaveh Shahbazian回答了一个解决方案。你没有给c.减少跑步的机会。c、 块运行一个无限的for循环,但它从不在该for循环中阻塞,只在每次迭代中反复调用continue。主线以100%的使用率无休止地旋转

但是,当您添加一个fmt.Print调用时,会生成一个syscall,从而允许另一个goroutine运行

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

您没有给c。减少运行的机会。c、 块运行一个无限的for循环,但它从不在该for循环中阻塞,只在每次迭代中反复调用continue。主线以100%的使用率无休止地旋转

但是,当您添加一个fmt.Print调用时,会生成一个syscall,从而允许另一个goroutine运行


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

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


有更好的方法来限制并发。例如,正如其他人所指出的,块永远不会屈服;goroutine不是一根线。您可以在运行时包中使用Gosched来强制屈服——但请注意,在块中以这种方式旋转是一个非常糟糕的想法


有更好的方法来限制并发。请参见一个示例,您正在寻找的是所谓的信号量。可以使用通道应用此模式

其思想是创建所需长度的缓冲通道。然后,当调用者想要释放资源时,通过将值放入通道并将其读回,使调用者获取资源。这样做会在程序中创建适当的同步点,以便Go计划程序正确运行


您现在正在做的是旋转cpu并阻塞Go调度程序。这取决于可用的CPU数量、Go的版本和GOMAXPROCS的价值。给定正确的组合,当您无限旋转该特定线程时,可能没有其他可用线程来服务其他goroutine

你要找的是一个信号量。可以使用通道应用此模式

其思想是创建所需长度的缓冲通道。然后,当调用者想要释放资源时,通过将值放入通道并将其读回,使调用者获取资源。这样做会在程序中创建适当的同步点,以便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)
}
Cond是一个同步实用程序,用于同时检查是否满足条件;而其他工作人员正在对该条件的数据进行变异

锁定和解锁功能的工作方式与我们期望的锁定方式相同。当我们完成检查或变异时,我们可以调用信号来唤醒一个goroutine或调用广播来唤醒多个goroutine,这样goroutine就知道可以自由地对数据进行操作或检查条件


唯一看起来不寻常的部分是等待功能。其实很简单。这就像调用Unlock并立即再次调用Lock,但Wait不会尝试再次锁定,除非由其他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)
}
Cond是一个同步实用程序,用于同时检查是否满足条件;而其他工作人员正在对该条件的数据进行变异

锁定和解锁功能的工作方式与我们期望的锁定方式相同。当我们完成检查或变异时,我们可以调用信号来唤醒一个goroutine或调用广播来唤醒多个goroutine,这样goroutine就知道可以自由地对数据进行操作或检查条件


唯一看起来不寻常的部分是等待功能。其实很简单。这就像调用Unlock并立即再次调用Lock,但Wait不会尝试再次锁定,除非由其他Goroutine中的信号或广播触发;就像正在变异条件数据的工作者一样。

您在这里似乎没有任何同步。@user2357112您的意思是c.Block,c.Block,c.减少可能发生?您在这里似乎没有任何同步。@user2357112您的意思是c.Block,c.Block,c.可能会出现下降?访问c.active时也存在竞争条件。sync包提供了一个Cond类型,可以在这里提供帮助。下面是修改为使用sync.Cond的上述代码的一部分。访问c.active时还存在竞争条件。sync包提供了一个Cond类型,可以在这里提供帮助。下面是修改为使用sync.Cond的上述代码的一部分。谢谢。“你能解释一下你在这里做了什么吗?”Yehonatan补充道,他解释了sync.Cond及其工作原理。我希望你觉得它有用!非常感谢!谢谢!非常感谢。“你能解释一下你在这里做了什么吗?”Yehonatan补充道,他解释了sync.Cond及其工作原理。我希望你觉得它有用!非常感谢!谢谢!