Multithreading 是否可以在每个外部进程不启动一个OS线程的情况下生成并与外部进程通信?

Multithreading 是否可以在每个外部进程不启动一个OS线程的情况下生成并与外部进程通信?,multithreading,unix,go,concurrency,elixir,Multithreading,Unix,Go,Concurrency,Elixir,短版: 在Golang中是否可以并行生成多个外部进程(shell命令),这样就不会为每个外部进程启动一个操作系统线程。。。并且在完成时仍然能够接收其输出 较长版本: 在Elixir中,如果使用端口,则可以生成数千个外部进程,而不会增加Erlang虚拟机中的线程数 例如,以下代码段启动2500个外部sleep进程,仅由Erlang VM下的20个操作系统线程管理: defmodule Exmultiproc do for _ <- 1..2500 do cmd = "sleep

短版:

在Golang中是否可以并行生成多个外部进程(shell命令),这样就不会为每个外部进程启动一个操作系统线程。。。并且在完成时仍然能够接收其输出

较长版本:

在Elixir中,如果使用端口,则可以生成数千个外部进程,而不会增加Erlang虚拟机中的线程数

例如,以下代码段启动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
因此,我想知道是否有一种方法可以编写Go代码,使其执行与Elixir代码类似的操作,而不是为每个外部进程打开一个操作系统线程

我基本上是在寻找一种方法来管理至少几千个外部长时间运行(最多10天)的进程,在操作系统中任何虚拟或物理限制都会导致尽可能少的问题

(对于代码中出现的任何错误,我深表歉意,因为我对长生不老药和安非他命都是新手。我渴望了解我所犯的任何错误。)


编辑:阐明了并行运行长时间运行的进程的要求。

我发现如果我们不等待进程,Go运行时将不会启动
2500个操作系统线程。因此,请使用cmd.Start()而不是cmd.Output()

但是,如果不使用golang OS包的OS线程,似乎不可能读取进程的
stdout
。我认为这是因为os包没有使用非块io来读取管道

下面的程序在我的Linux上运行得很好,尽管它阻止了进程的标准输出,正如@JimB在评论中所说的,可能是因为我们的输出很小,并且适合系统缓冲区

func main() {
    concurrentProcessCount := 50
    wtChan := make(chan *result, concurrentProcessCount)
    for i := 0; i < concurrentProcessCount; i++ {
        go func(i int) {
            fmt.Println("Starting process ", i, "...")
            cmd := exec.Command("bash", "-c", "for i in 1 2 3 4 5; do echo to sleep $i seconds;sleep $i;echo done;done;")
            outPipe,_ := cmd.StdoutPipe()
            err := cmd.Start()
            if err != nil {
                panic(err)
            }
            <-time.Tick(time.Second)
            fmt.Println("Finishing process ", i, "...")
            wtChan <- &result{cmd.Process, outPipe}
        }(i)
    }

    fmt.Println("root:",os.Getpid());

    waitDone := 0
    forLoop:
    for{
        select{
        case r:=<-wtChan:
            r.p.Wait()
            waitDone++
            output := &bytes.Buffer{}
            io.Copy(output, r.b)
            fmt.Println(waitDone, output.String())
            if waitDone == concurrentProcessCount{
                break forLoop
            }
        }
    }
}
func main(){
concurrentProcessCount:=50
wtChan:=make(chan*结果,concurrentProcessCount)
对于i:=0;i这肯定不是对所提出的问题的回答,但1)我知道Go可以启动OS线程,让代码在阻止系统调用期间保持运行;我不知道这些线程是否具有大堆栈分配,例如,您倾向于将其与pthreads线程或类似线程相关联;2)我认为该进程的fork+exec可能占主导地位这个操作的成本和程序端的线程相比是很小的。@Two:是的,Go线程似乎并不是很重。但是,根据我的实验,它们似乎仍然限制了可以实际管理的外部进程的数量,至少比Elixir中的要多,因为(小)额外成本。一个线程是一个操作系统线程,也称为pthread,与goroutine完全分离。生成一个子进程可能会消耗多达三个在读/写常用文件描述符时被阻止的goroutine。计划程序还将生成三个操作系统线程,以替换在读/写操作时被阻止的goroutine。pro有多大问题是什么?我不知道,但这取决于有多少线程太多,而有多少打开的文件描述符太多。我在UbuntuAMD64上测试了go1.5下的10个线程和100个线程。90个额外打开的进程中的每一个都会增加驻留大小的成本(大约是物理内存占用)计算结果为30.4kb。虚拟内存大小增加了108kb/进程,但根据定义,这并不意味着占用了108kb的物理RAM。您可以运行更好的测试,但
this>什么都没有
我想。谢谢Jiang,我也尝试了这种方法,但它也启动了同样多的线程。在您的代码中,您必须进行大量睡眠秒数(比如3600,也就是一个小时),而不仅仅是一个回音。另外,我认为在go例程中,wg.Done()之前必须有一个cmd.Wait(),以防止它完成,直到命令完成。当我解决这个问题时,我会得到相同的结果,请参见:但是,我所说的不是等待:(我认为等待会消耗一个线程,不能与其他goroutine一起重用。是的,但这样就无法获得这些进程的输出,这对我来说是非常重要的一点,在问题中也提到了。当然,我们可以读取进程的输出,即使没有等待。这是另一个问题,如
如何读取进程的输出如果没有等待进程完成
。这仍然是不正确的。首先,调用wait直到所有内容都通过StdoutPipe读取是不正确的,因为wait隐式地尝试关闭管道。其次,您正在阻止进程的输出,如果命令没有充分缓冲其输出,这将导致死锁。与命令无关:你正在泄漏时间。Ticker-如果你需要选择时间间隔,请使用
时间。在
时间之后。Ticker
你可以停止,但在这种情况下,只需使用
时间。Sleep
,或者更好的是不要使用任何东西,因为你不需要睡觉就可以绕过比赛。
func main() {
    concurrentProcessCount := 50
    wtChan := make(chan *result, concurrentProcessCount)
    for i := 0; i < concurrentProcessCount; i++ {
        go func(i int) {
            fmt.Println("Starting process ", i, "...")
            cmd := exec.Command("bash", "-c", "for i in 1 2 3 4 5; do echo to sleep $i seconds;sleep $i;echo done;done;")
            outPipe,_ := cmd.StdoutPipe()
            err := cmd.Start()
            if err != nil {
                panic(err)
            }
            <-time.Tick(time.Second)
            fmt.Println("Finishing process ", i, "...")
            wtChan <- &result{cmd.Process, outPipe}
        }(i)
    }

    fmt.Println("root:",os.Getpid());

    waitDone := 0
    forLoop:
    for{
        select{
        case r:=<-wtChan:
            r.p.Wait()
            waitDone++
            output := &bytes.Buffer{}
            io.Copy(output, r.b)
            fmt.Println(waitDone, output.String())
            if waitDone == concurrentProcessCount{
                break forLoop
            }
        }
    }
}