How do I fork a go process?
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 supposed 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?
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
}
syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
might work, and first return value is id you want.
Here's an example:
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)
}
}
then get output similar to this:
In parent: 16397 4 11
In child: 0 5 10