I want to fork a go process and get back the id of the new process(es), but all I can see in the exec
or os
libraries is to start a new process.
问题:
回答1:
You supposedly want syscall.ForkExec()
from the syscall
package.
Note that fork()
has been invented at the time when no threads were used at all, and a process had always had just a single thread of execution in it, and hence forking it was safe. With Go, the situation is radically different as it heavily uses OS-level threads to power its goroutine scheduling.
Now, unadorned fork(2)
on Linux will make the child process have just the single thread—the one which called fork(2)
in the parent process—among all those which were active, including some crucial threads used by the Go runtime. Basically this means that you simply cannot expect the child process to be able to continue executing Go code, and the only thing you can sensibly do is to somehow immediately perform exec(2)
. Notice that that's what syscall.ForkExec()
is suppsed to be used for.
And now think about the problem further. I'd say these days the only thing a direct call to fork(2)
is useful for is "best-effort asynchronous process state snapshotting"—the kind, say, Redis uses. This technique relies on the fact the child process inherits all the memory data pages from its parent, but the OS uses copy-on-write technique to not really copy all that data, so the child can just sit there and save all the data structures to disk while its parent is chugging away modifying them in its own address space. Every other conceivable use for fork()
implies immediate exec()
, and that's what exec.Command()
et al is for, so why just not use it?
回答2:
One solution is to use a exec.Command
executed in its goroutine.
That is what the little project akshaydeo/go_process
does:
// 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
shows some examples:
// 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
}