Voglio avviare un processo di go e recuperare l'id dei nuovi processi, ma tutto quello che posso vedere nelle librerie exec
o os
è avviare una nuova procedura.Come si esegue il fork di un processo go?
risposta
È presumibilmente vogliono syscall.ForkExec()
dal pacchetto syscall
.
Si noti che fork()
è stato inventato nel momento in cui non sono stati utilizzati affatto thread e un processo aveva sempre avuto solo un singolo thread di esecuzione in esso, e quindi la creazione di un fork era sicuro. Con Go, la situazione è radicalmente diversa in quanto utilizza intensamente thread a livello di sistema operativo per alimentare la sua pianificazione di goroutine.
Ora, disadorno fork(2)
su Linux farà il processo figlio ha solo il singolo thread — quella che chiama fork(2)
nel processo padre — tra tutte quelle che erano attivi, tra cui alcune discussioni cruciali utilizzati dal runtime Go. Fondamentalmente questo significa che semplicemente non può aspettarsi che il figlio procss sia in grado di continuare a eseguire il codice Go, e l'unica cosa che si può sensibilmente fare è in qualche modo eseguire immediatamente exec(2)
. Si noti che questo è ciò per cui è previsto l'uso di syscall.ForkExec()
.
E ora pensa al problema ulteriormente. In questi giorni direi che l'unica cosa che una chiamata diretta a fork(2)
è utile per "lo snapshot dello stato del processo asincrono best-effort" — per il tipo, ad esempio, Redis. Questa tecnica si basa sul fatto che il processo figlio eredita tutte le pagine di dati di memoria dal suo genitore, ma il sistema operativo usa la tecnica copy-on-write per non copiare veramente tutti quei dati, così il bambino può semplicemente sedersi e salvare tutte le strutture dati su disco mentre il suo genitore sta scompigliando modificandoli nel proprio spazio indirizzo. Ogni altro uso concepibile per fork()
implica l'immediato exec()
, ed è per questo che è exec.Command()
et al, quindi perché non usarlo?
Una soluzione è utilizzare un exec.Command
eseguito nella sua goroutine.
Questo è quello che il piccolo progetto akshaydeo/go_process
fa:
// 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
mostra alcuni esempi:
// 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
}
Si sta creando un processo 'ls'. Voglio dare lo stesso processo che sto eseguendo. Come posso farlo - 'fork' di' C++ '? – Shashwat
@Shashwat non sono sicuro: ho visto solo il contrario (http://stackoverflow.com/q/22805059/6309) – VonC
Sto indovinando qui: c'è un 'return' mancante nel blocco' if err! = Nil' di 'Fork'. –
"Ogni altro uso concepibile per fork() implica exec immediato(), ed è per questo che exec.Command() et al è, quindi perché non usarlo?" C'è setrlimit(), impostazione di SELinux, chroot() ing, chiusura o impostazione di FDs prima di passare il controllo - e questi devono avvenire tra fork() ed exec(), no? –
E se "lo snapshot dello stato del processo asincrono best-effort" è * precisamente * cosa sto cercando di fare? – cpcallen
@cpcallen, temo, sarebbe "not in go" * in questo modo. * L'essenza del problema è che il runtime di Go fa due cose ai thread a livello di sistema operativo: a) li usa per alimentare le tue goroutine ; b) li usa per i propri bisogni.Poiché solo un singolo thread sopravvive a un fork() ', non appena viene ripreso in un nuovo processo, sarà in uno stato semi-assed, impossibile da funzionare correttamente. – kostix