Goroutines是合作安排的。这是否意味着不会产生执行的goroutines会导致goroutines一个接一个地运行?

时间:2016-05-26 20:02:54

标签: multithreading go scheduling goroutine

来自:http://blog.nindalf.com/how-goroutines-work/

  

由于goroutine是合作安排的,连续循环的goroutine可以在同一个线程上饿死其他goroutine。

     

Goroutines很便宜,并且如果它们被阻塞,不会导致它们被多路复用的线程阻塞

     
      
  • 网络输入
  •   
  •   
  • 频道操作或
  •   
  • 阻止同步包中的原语。
  •   

所以考虑到上述情况,假设你有一些像这样的代码,除了循环一个随机的次数并打印总和之外什么都不做:

func sum(x int) {
  sum := 0
  for i := 0; i < x; i++ {
    sum += i
  }
  fmt.Println(sum)
}

如果你使用像

这样的goroutines
go sum(100)
go sum(200)
go sum(300)
go sum(400)

如果你只有一个线程,goroutines会一个接一个地运行吗?

4 个答案:

答案 0 :(得分:2)

汇编和整理所有克里克的评论。

抢占意味着内核(运行时)允许线程运行特定的时间,然后在没有它们执行或知道任何事情的情况下将执行权交给其他线程。在OS内核中,通常使用硬件中断实现。进程无法阻止整个操作系统。在协作式多任务中,线程必须明确地向其他人执行执行。如果它不能阻止整个过程甚至整个机器。这就是Go如何做到的。它有一些非常具体的点,goroutine可以产生执行。但是如果goroutine只为{}执行,那么它将锁定整个进程。

但是,引用并未提及运行时的最新更改。 fmt.Println(sum)可能导致其他goroutine被调度,因为较新的运行时将在函数调用上调用调度程序。

如果你没有任何函数调用,只是一些数学,那么是的,goroutine将锁定该线程,直到它退出或击中可能导致其他人执行的内容。这就是for {}在Go中不起作用的原因。更糟糕的是,即使GOMAXPROCS&gt;它仍将导致流程挂起。 1因为GC的工作原理,但无论如何你都不应该依赖它。理解这些东西很好,但不要指望它。甚至有人建议在像你这样的循环中插入调度程序调用

Go的运行时所做的主要是它让每个人都能执行并且不会让任何人挨饿。如何做到这一点未在语言规范中指定,并可能在将来发生变化。如果关于循环的提议将被实现,那么即使没有函数调用切换也可能发生。目前你应该记住的唯一事情是,在某些情况下,函数调用可能会导致goroutine产生执行。

为了解释Akavall的回答,当调用fmt.Printf时,它首先要检查是否需要增加堆栈并调用调度程序。它可能切换到另一个goroutine。它是否会切换取决于其他goroutine的状态和调度程序的确切实现。像任何调度程序一样,它可能会检查是否存在应该执行的饥饿goroutine。通过许多迭代,函数调用有更大的机会进行切换,因为其他人正在挨饿。只有很少的迭代才能在饥饿发生之前完成goroutine。

答案 1 :(得分:1)

嗯,让我们说@Component @Scope("prototype") public class Foo { } 是1.每次同时运行一个goroutines。 Go的调度程序只是将其中一个产生的goroutine放在一定时间,然后放到另一个等等,直到完成所有操作。

因此,您永远不知道在给定时间运行哪个goroutine,这就是您需要同步变量的原因。从您的示例来看,runtime.GOMAXPROCS不太可能完全运行,sum(100)将完全运行,等等

最有可能的是,一个goroutine会做一些迭代,然后另一个会做一些,然后再做一次等。

所以,总的来说,它们不是连续的,即使一次只有一个goroutine活跃(GOMAXPROCS = 1)。

那么,使用goroutines的优点是什么?大量。这意味着你可以在goroutine中进行操作,因为它并不重要并继续主程序。想象一下HTTP网络服务器。在goroutine中处理每个请求很方便,因为你不必关心排队并按顺序运行它们:你让Go的调度程序完成这项工作。

另外,有时goroutine处于非活动状态,因为您调用了sum(200),或者他们正在等待事件,比如接收某个频道的内容。 Go可以看到这一点,只是执行其他goroutine,而有些则处于空闲状态。

我知道我没有提供一些优势,但我不太了解并发性,而是告诉你关于它们的事情。

编辑:

与您的示例代码相关,如果您在通道末尾添加每个迭代,在一个处理器上运行该迭代并打印通道的内容,您将看到goroutines之间没有上下文切换:每个一个完成另一个后顺序运行。

但是,一般规则,并且未在语言中指定。因此,您不应该依赖这些结果来得出一般性结论。

答案 2 :(得分:1)

它的价值。我可以创建一个简单的例子,很明显goroutines不会一个接一个地运行:

package main

import (
    "fmt"
    "runtime"
)

func sum_up(name string, count_to int, print_every int, done chan bool) {
    my_sum := 0
    for i := 0; i < count_to; i++ {
        if i % print_every == 0 {
            fmt.Printf("%s working on: %d\n", name, i)
        }
        my_sum += 1
    }
    fmt.Printf("%s: %d\n", name, my_sum)
    done <- true 
}

func main() {
    runtime.GOMAXPROCS(1)
    done := make(chan bool)

    const COUNT_TO =   10000000
    const PRINT_EVERY = 1000000

    go sum_up("Amy", COUNT_TO, PRINT_EVERY, done)
    go sum_up("Brian", COUNT_TO, PRINT_EVERY, done)

    <- done 
    <- done 

}

结果:

....
Amy working on: 7000000
Brian working on: 8000000
Amy working on: 8000000
Amy working on: 9000000
Brian working on: 9000000
Brian: 10000000
Amy: 10000000

此外,如果我添加一个只执行永久循环的函数,那将阻止整个过程。

func dumb() {
    for {

    }
}

这会在某个随机点阻止:

go dumb()
go sum_up("Amy", COUNT_TO, PRINT_EVERY, done)
go sum_up("Brian", COUNT_TO, PRINT_EVERY, done)

答案 3 :(得分:0)

@Akavall在创建哑goroutine之后尝试添加睡眠,goruntime永远不会执行sum_up goroutine。

看来go运行时会立即生成下一个go例程,它可能会执行sum_up goroutine,直到go运行时调度dumb()goroutine运行。一旦dumb()计划运行,那么go运行时就不会调度sum_up goroutines运行,因为dumb运行{}