我如何 fork 一个 go 进程?
How do I fork a go process?
我想分叉一个 go 进程并取回新进程的 ID,但我在 exec
或 os
库中只能看到启动一个新进程.
一种解决方案是使用 exec.Command
在其 goroutine 中执行。
这就是小项目 akshaydeo/go_process
所做的:
// Method to fork a process for given command
// and return ProcessMonitor
func Fork(processStateListener ProcessStateListener, cmdName string, cmdArgs ...string) {
go func() {
processMonitor := &ProcessMonitor{}
args := strings.Join(cmdArgs, ",")
command := exec.Command(cmdName, args)
output, err := command.Output()
if err != nil {
processMonitor.Err = err
processStateListener.OnError(processMonitor, err)
}
processMonitor.Output = &output
processStateListener.OnComplete(processMonitor)
}()
}
The test process_test.go
显示一些示例:
// Test case for fork
func TestFork(t *testing.T) {
processStateListenerImpl := &ProcessStateListenerImpl{make(chan bool)}
Fork(processStateListenerImpl,"ls", "-a") //("ping","192.168.3.141","-c","3")
// waiting onto monitor
<-processStateListenerImpl.monitor
}
您应该想要 syscall
包中的 syscall.ForkExec()
。
请注意,fork()
是在根本没有使用线程的时候发明的,一个进程中始终只有一个执行线程,因此分叉它是安全的。对于 Go,情况完全不同,因为它大量使用 OS 级线程来为其 goroutine 调度提供动力。
现在,Linux 上的朴素 fork(2)
将使子进程在所有活动线程中只有一个线程——在父进程中调用 fork(2)
的线程,包括 Go 运行时使用的一些关键线程。基本上这意味着你 不能指望子进程能够继续执行 Go 代码, 并且你唯一可以明智地做的就是以某种方式立即执行 exec(2)
。请注意,这就是 syscall.ForkExec()
的用途。
现在进一步思考这个问题。我想说这些天直接调用 fork(2)
唯一有用的是 "best-effort asynchronous process state snapshotting" — 比如说 Redis 使用的那种。该技术依赖于子进程从其父进程继承所有内存数据页这一事实,但是 OS 使用写时复制技术并没有真正复制所有数据,因此子进程可以坐在那里保存将所有数据结构写入磁盘,而其父级正在修改它们自己的地址 space。 fork()
的所有其他可能的用途都意味着立即 exec()
,这就是 exec.Command()
等人的用途,那么为什么不使用它呢?
syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
可能有效,第一个 return 值是你想要的 id。
这是一个例子:
func main() {
foo := 4
bar := 10
id, _, _ := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
if id == 0 {
foo++
fmt.Println("In child:", id, foo, bar)
} else {
bar++
fmt.Println("In parent:", id, foo, bar)
}
}
然后得到类似这样的输出:
In parent: 16397 4 11
In child: 0 5 10
我想分叉一个 go 进程并取回新进程的 ID,但我在 exec
或 os
库中只能看到启动一个新进程.
一种解决方案是使用 exec.Command
在其 goroutine 中执行。
这就是小项目 akshaydeo/go_process
所做的:
// Method to fork a process for given command
// and return ProcessMonitor
func Fork(processStateListener ProcessStateListener, cmdName string, cmdArgs ...string) {
go func() {
processMonitor := &ProcessMonitor{}
args := strings.Join(cmdArgs, ",")
command := exec.Command(cmdName, args)
output, err := command.Output()
if err != nil {
processMonitor.Err = err
processStateListener.OnError(processMonitor, err)
}
processMonitor.Output = &output
processStateListener.OnComplete(processMonitor)
}()
}
The test process_test.go
显示一些示例:
// Test case for fork
func TestFork(t *testing.T) {
processStateListenerImpl := &ProcessStateListenerImpl{make(chan bool)}
Fork(processStateListenerImpl,"ls", "-a") //("ping","192.168.3.141","-c","3")
// waiting onto monitor
<-processStateListenerImpl.monitor
}
您应该想要 syscall
包中的 syscall.ForkExec()
。
请注意,fork()
是在根本没有使用线程的时候发明的,一个进程中始终只有一个执行线程,因此分叉它是安全的。对于 Go,情况完全不同,因为它大量使用 OS 级线程来为其 goroutine 调度提供动力。
现在,Linux 上的朴素 fork(2)
将使子进程在所有活动线程中只有一个线程——在父进程中调用 fork(2)
的线程,包括 Go 运行时使用的一些关键线程。基本上这意味着你 不能指望子进程能够继续执行 Go 代码, 并且你唯一可以明智地做的就是以某种方式立即执行 exec(2)
。请注意,这就是 syscall.ForkExec()
的用途。
现在进一步思考这个问题。我想说这些天直接调用 fork(2)
唯一有用的是 "best-effort asynchronous process state snapshotting" — 比如说 Redis 使用的那种。该技术依赖于子进程从其父进程继承所有内存数据页这一事实,但是 OS 使用写时复制技术并没有真正复制所有数据,因此子进程可以坐在那里保存将所有数据结构写入磁盘,而其父级正在修改它们自己的地址 space。 fork()
的所有其他可能的用途都意味着立即 exec()
,这就是 exec.Command()
等人的用途,那么为什么不使用它呢?
syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
可能有效,第一个 return 值是你想要的 id。
这是一个例子:
func main() {
foo := 4
bar := 10
id, _, _ := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
if id == 0 {
foo++
fmt.Println("In child:", id, foo, bar)
} else {
bar++
fmt.Println("In parent:", id, foo, bar)
}
}
然后得到类似这样的输出:
In parent: 16397 4 11
In child: 0 5 10