【发布时间】:2015-11-27 00:19:50
【问题描述】:
短版:
Golang 是否可以并行生成多个外部进程(shell 命令),这样它就不会为每个外部进程启动一个操作系统线程。 .. 完成后仍然能够接收到它的输出吗?
加长版:
在 Elixir 中,如果您使用端口,则可以生成数千个外部进程,而不会真正增加 Erlang 虚拟机中的线程数。
例如以下代码 sn-p 启动了 2500 个外部 sleep 进程,仅由 Erlang VM 下的 20 个操作系统线程管理:
defmodule Exmultiproc do
for _ <- 1..2500 do
cmd = "sleep 3600"
IO.puts "Starting another process ..."
Port.open({:spawn, cmd}, [:exit_status, :stderr_to_stdout])
end
System.cmd("sleep", ["3600"])
end
(前提是您将ulimit -n 设置为较大的数字,例如10000)
另一方面,Go 中的以下代码应该做同样的事情 - 启动 2500 个外部 sleep 进程 - 也启动 2500 个操作系统线程。因此,它显然会为每个(阻塞?)系统调用启动一个操作系统线程(以免阻塞整个 CPU 或类似的,如果我理解正确的话):
package main
import (
"fmt"
"os/exec"
"sync"
)
func main() {
wg := new(sync.WaitGroup)
for i := 0; i < 2500; i++ {
wg.Add(1)
go func(i int) {
fmt.Println("Starting sleep ", i, "...")
cmd := exec.Command("sleep", "3600")
_, err := cmd.Output()
if err != nil {
panic(err)
}
fmt.Println("Finishing sleep ", i, "...")
wg.Done()
}(i)
}
fmt.Println("Waiting for WaitGroup ...")
wg.Wait()
fmt.Println("WaitGroup finished!")
}
因此,我想知道是否有一种方法可以编写 Go 代码,使其执行与 Elixir 代码类似的事情,而不是为每个外部进程打开一个操作系统线程?
我基本上是在寻找一种方法来管理至少几千个外部长期运行(最多 10 天)的进程,以尽可能减少操作系统中任何虚拟或物理限制的问题.
(对于代码中的任何错误,我深表歉意,因为我是 Elixir 和 Go 的新手。我很想知道我正在做的任何错误。)
编辑:澄清了并行运行长时间运行的进程的要求。
【问题讨论】:
-
这绝对不是对所提出的 q 的回答,但 1)我知道 Go 可以启动 OS 线程以让代码在阻塞系统调用期间继续运行;我不知道这些线程是否具有,例如,您倾向于与 pthreads 线程或类似线程相关联的大堆栈分配,以及 2)我认为进程的 fork+exec 将主导成本似乎是合理的这个操作,和你程序这边的线程比较小。
-
@twotwotwo:确实,Go 线程似乎并不是非常重。但是,根据我的实验,它们似乎仍然限制了可以实际管理的外部进程的数量,至少比 Elixir 更多,因为(小)额外成本。
-
线程是 OS 线程,也称为 pthread,与 goroutine 完全分离。
-
生成一个子进程可能会消耗多达三个在读取/写入常用文件描述符时阻塞的 goroutine。调度程序还将产生三个操作系统线程来替换那些因阻塞读/写操作而消耗的线程。这是多大的问题?我不知道,但这取决于有多少线程太多,而有多少打开的文件描述符太多了。
-
我在Ubuntu amd64上测试了go1.5下的10线程和100线程。 90 个额外打开的进程中的每一个在驻留大小(大致占用的物理内存)中的增量成本计算为 30.4kb。虚拟内存大小增加了 108kb/进程,但根据定义,这并不意味着占用了 108kb 物理 RAM。你可以运行更好的测试,但
this > nothing我想。
标签: multithreading unix go concurrency elixir