协程(goroutines)适用于大规模、并行、计算密集型问题吗?

huangapple go评论139阅读模式
英文:

Are goroutines appropriate for large, parallel, compute-bound problems?

问题

Go语言中的goroutine是一种轻量级的线程,可以用于并发执行任务。goroutine是由Go运行时系统进行调度的,它使用了一种称为"抢占式调度"的策略,这意味着在多核计算机上运行500个计算密集型的goroutine时,可以期望合理地平衡负载,而不需要手动进行协作式的"yield"操作。

因此,对于你描述的问题领域,如果你需要实时计算密集型的流数据分析,并且希望能够充分利用多核计算机的性能,Go语言的goroutine是一个很好的选择。通过将每个问题映射到一个goroutine,你可以实现并发地解决大量的问题,并且利用Go语言提供的通道机制来实现问题之间的通信和同步。

当然,如果你对其他语言也感兴趣,可以考虑Julia、Rust或者一些函数式语言。但需要注意的是,这些语言可能没有像Go语言那样内置支持通道机制,因此在实现类似功能时可能需要额外的工作。

总之,如果你希望在计算密集型任务中实现并发和高性能,并且对轻量级线程的支持有要求,Go语言是一个很好的选择。

英文:

Are go-routines pre-emptively multitasked for numerical problems?

I am very intrigued by the lean design of Go, the speed, but most by the fact that channels are first-class objects. I hope the last point may enable a whole new class of deep-analysis algorithms for big data, via the complex interconnection patterns which they should allow.

My problem domain requires real-time compute-bound analysis of streaming incoming data. The data can be partitioned into between 100-1000 "problems" each of which will take between 10 and 1000 seconds to compute (ie their granularity is highly variable). Results must however all be available before the output makes sense, ie, say 500 problems come in, and all 500 must be solved before I can use any of them. The application must be able to scale, potentially to thousands (but unlikely 100s of thousands) problems.

Given that I am less worried about numerical library support (most of this stuff is custom), Go seems ideal as I can map each problem to a goroutine. Before I invest in learning Go rather than say, Julia, Rust, or a functional language (none of which, as far as I can see, have first-class channels so for me are at an immediate disadvantage) I need to know if goroutines are properly pre-emptively multi-tasked. That is, if I run 500 compute-bound goroutines on a powerful multicore computer, can I expect reasonably load balancing across all the "problems" or will I have to cooperatively "yield" all the time, 1995-style. This issue is particularly important given the variable granularity of the problem and the fact that, during compute, I usually will not know how much longer it will take.

If another language would serve me better, I am happy to hear about it, but I have a requirement that threads (or go/coroutines) of execution be lightweight. Python multiprocessing module for example, is far too resource intensive for my scaling ambitions. Just to pre-empt: I do understand the difference between parallelism and concurrency.

答案1

得分: 11

Go运行时具有一种模型,其中多个Go协程以自动方式映射到多个线程上。没有任何Go协程绑定到特定的线程,调度器可以(并且将)将Go协程调度到下一个可用的线程上。Go程序使用的线程数取决于GOMAXPROCS环境变量,并且可以使用runtime.GOMAXPROCS进行覆盖。这是一个简化的描述,足以理解。

以下情况下,Go协程可能会让出:

  • 在任何可能阻塞的操作上,即任何无法立即返回结果的操作,因为它要么是(可能的)阻塞系统调用,比如io.Read(),要么是可能需要等待其他Go协程的操作,比如获取互斥锁或从通道发送或接收。
  • 在各种运行时操作上
  • 如果调度器检测到被抢占的Go协程占用了大量CPU时间,则在函数调用时
  • 在调用runtime.Gosched
  • 在panic()时
  • 从Go 1.14开始,紧密循环可以被运行时抢占。因此,没有函数调用的循环不再可能导致调度器死锁或显著延迟垃圾回收。这在所有平台上都不受支持-请务必查看发布说明。还请参阅问题#36365以了解此领域的未来计划。
  • 在其他各种场合

以下情况会阻止Go协程让出:

英文:

The Go runtime has a model where multiple Go routines are mapped onto multiple threads in an automatic fashion. No Go routine is bound to a certain thread, the scheduler may (and will) schedule Go routines to the next available thread. The number of threads a Go program uses is taken from the GOMAXPROCS environment variable and can be overriden with runtime.GOMAXPROCS(). This is a simplified description which is sufficient for understanding.

Go routines may yield in the following cases:

  • On any operation that might block, i.e. any operation that cannot return a result on the spot because it is either a (possible) blocking system-call like io.Read() or an operation that might require waiting for other Go routines, like acquiring a mutex or sending to or receiving from a channel
  • On various runtime operations
  • On function call if the scheduler detects that the preempted Go routine took a lot of CPU time (this is new in Go 1.2)
  • On call to runtime.Gosched()
  • On panic()
  • As of Go 1.14, tight loops can be preempted by the runtime. As a result, loops without function calls no longer potentially deadlock the scheduler or significantly delay garbage collection. This is not supported on all platforms - be sure to review the release notes. Also see issue #36365 for future plans in this area.
  • On various other occasions

The following things prevent a Go routine from yielding:

答案2

得分: 1

我不确定我完全理解你的意思,但是你可以设置runtime.GOMAXPROCS来扩展到所有的处理器,然后使用通道(或锁)来同步数据,以下是一个示例:

const N = 100

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU()) //扩展到所有处理器
    var stuff [N]bool
    var wg sync.WaitGroup
    ch := make(chan int, runtime.NumCPU())
    done := make(chan struct{}, runtime.NumCPU())
    go func() {
        for i := range ch {
            stuff[i] = true
        }
    }()
    wg.Add(N)
    for i := range stuff {
        go func(i int) {

            for { //cpu bound loop
                select {
                case <-done:
                    fmt.Println(i, "is done")
                    ch <- i
                    wg.Done()
                    return
                default:
                }
            }
        }(i)
    }
    go func() {
        for _ = range stuff {
            time.Sleep(time.Microsecond)
            done <- struct{}{}
        }
        close(done)
    }()
    wg.Wait()
    close(ch)
    for i, v := range stuff { //false-postive datarace
        if !v {
            panic(fmt.Sprintf("%d != true", i))
        }
    }
    fmt.Println("All done")
}

编辑:关于调度器的信息,请参考http://tip.golang.org/src/pkg/runtime/proc.c

Goroutine调度器

调度器的工作是将准备好运行的goroutine分发给工作线程。

主要概念有:

  • G - goroutine。
  • M - 工作线程,或机器。
  • P - 处理器,执行Go代码所需的资源。M必须有一个关联的P来执行Go代码,但它可以被阻塞或在系统调用中没有关联的P。

设计文档请参考http://golang.org/s/go11sched。

英文:

Not sure I fully understand you, however you can set runtime.GOMAXPROCS to scale to all processes, then use channels (or locks) to synchronize the data, example:

const N = 100

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) //scale to all processors
	var stuff [N]bool
	var wg sync.WaitGroup
	ch := make(chan int, runtime.NumCPU())
	done := make(chan struct{}, runtime.NumCPU())
	go func() {
		for i := range ch {
			stuff[i] = true
		}
	}()
	wg.Add(N)
	for i := range stuff {
		go func(i int) {

			for { //cpu bound loop
				select {
				case &lt;-done:
					fmt.Println(i, &quot;is done&quot;)
					ch &lt;- i
					wg.Done()
					return
				default:
				}
			}
		}(i)
	}
	go func() {
		for _ = range stuff {
			time.Sleep(time.Microsecond)
			done &lt;- struct{}{}
		}
		close(done)
	}()
	wg.Wait()
	close(ch)
	for i, v := range stuff { //false-postive datarace
		if !v {
			panic(fmt.Sprintf(&quot;%d != true&quot;, i))
		}
	}
	fmt.Println(&quot;All done&quot;)
}

EDIT: Information about the scheduler @ http://tip.golang.org/src/pkg/runtime/proc.c

> ## Goroutine scheduler
> The scheduler's job is to distribute ready-to-run goroutines over worker threads.

> The main concepts are:
>
> * G - goroutine.
> * M - worker thread, or machine.
> * P - processor, a resource that is required to execute Go code. M must have an associated P to execute Go code, however it can be blocked or in a syscall w/o an associated P.
>
> Design doc at http://golang.org/s/go11sched.

huangapple
  • 本文由 发表于 2014年6月8日 20:36:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/24106228.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定