la scrittura simultanea su stdout è sicura?
il codice riportato di seguito non genera una corsa di dati
package main
import (
"fmt"
"os"
"strings"
)
func main() {
x := strings.Repeat(" ", 1024)
go func() {
for {
fmt.Fprintf(os.Stdout, x+"aa\n")
}
}()
go func() {
for {
fmt.Fprintf(os.Stdout, x+"bb\n")
}
}()
go func() {
for {
fmt.Fprintf(os.Stdout, x+"cc\n")
}
}()
go func() {
for {
fmt.Fprintf(os.Stdout, x+"dd\n")
}
}()
<-make(chan bool)
}
Ho provato più lunghezze di dati, con variante https://play.golang.org/p/29Cnwqj5K30
Questo post dice che non è TS.
Questa mail non risponde realmente alla domanda, o non ho capito.
La documentazione dei pacchetti di os e fmt non ne parla molto. Ammetto di non aver scavato il codice sorgente di quei due pacchetti per trovare ulteriori spiegazioni, mi sembrano troppo complessi.
Quali sono le raccomandazioni e i loro riferimenti ?
Risposte
Non sono sicuro che si qualificherebbe come una risposta definitiva, ma cercherò di fornire alcune informazioni.
Le F*funzioni-del fmtpacchetto si limitano a dichiarare che prendono un valore di un tipo che implementa l' io.Writerinterfaccia e lo chiamano Write. Le funzioni stesse sono sicure per l'uso simultaneo, nel senso che va bene chiamare un numero qualsiasi di fmt.Fwhavetersimultaneamente: il pacchetto stesso è preparato per questo, ma il supporto di un'interfaccia in Go non indica nulla sul tipo reale in termini di concorrenza.
In altre parole, il punto reale in cui la concorrenza può o non può essere consentita è rimandato allo "scrittore" a cui le funzioni di fmtscrittura. (Si dovrebbe anche tenere presente che le fmt.*Print*funzioni possono chiamare la Writesua destinazione un numero qualsiasi di volte, a differenza di quelle fornite dal pacchetto di riserva log.)
Quindi, fondamentalmente abbiamo due casi:
- Implementazioni personalizzate di
io.Writer. - Stock implementazioni di esso, come
*os.Fileo wrapper attorno ai socket prodotti dalle funzioni dinetpackage.
Il primo caso è quello semplice: qualunque cosa abbia fatto l'implementatore.
Il secondo caso è più difficile: da quanto ho capito, la posizione della libreria standard Go su questo (anche se non chiaramente indicata nei documenti) in quanto i wrapper che fornisce intorno alle "cose" fornite dal sistema operativo, come i descrittori di file e i socket, sono ragionevolmente "thin", e quindi qualsiasi semantica implementata, è implementata transitivamente dal codice stdlib in esecuzione su un particolare sistema.
Ad esempio, POSIX richiede che le write(2)chiamate siano atomiche l'una rispetto all'altra quando operano su file regolari o collegamenti simbolici. Ciò significa che, poiché qualsiasi chiamata a Writecose che avvolgono descrittori di file o socket si traduce effettivamente in una singola chiamata di sistema di "scrittura" del sistema tagret, è possibile consultare i documenti del sistema operativo di destinazione e farsi un'idea di cosa accadrà.
Nota che POSIX parla solo degli oggetti del filesystem, e se os.Stdoutè aperto a un terminale (o uno pseudo-terminale) o a un pipe o qualsiasi altra cosa che supporti la write(2)syscall, i risultati dipenderanno da cosa sia il sottosistema pertinente e / o il driver implementare, ad esempio, i dati di più chiamate simultanee potrebbero essere intervallati o una delle chiamate, o entrambe, potrebbe essere semplicemente fallita dal sistema operativo: improbabile, ma ancora.
Tornando a Go, da quello che ho raccolto, i seguenti fatti valgono per i tipi Go stdlib che avvolgono descrittori di file e socket:
- Sono sicuri per l'uso simultaneo da soli (voglio dire, a livello Go).
- Esse "mappano"
WriteeReadchiamano 1 a 1 all'oggetto sottostante, ovvero unaWritechiamata non viene mai divisa in due o più chiamate di sistema sottostanti e unaReadchiamata non restituisce mai dati "incollati" dai risultati di più chiamate di sistema sottostanti. (A proposito, le persone occasionalmente vengono inciampate da questo comportamento senza fronzoli, ad esempio, vedi questo o questo come esempi.)
Quindi, fondamentalmente, se lo consideriamo con il fatto che fmt.*Print*sei libero di chiamare Writeun numero qualsiasi di volte per una singola chiamata, i tuoi esempi che utilizzano os.Stdout, saranno:
- Non comportare mai una gara di dati, a meno che tu non abbia assegnato alla variabile
os.Stdoutun'implementazione personalizzata, ma - I dati effettivamente scritti nell'FD sottostante verranno mescolati in un ordine imprevedibile che può dipendere da molti fattori tra cui la versione e le impostazioni del kernel del sistema operativo, la versione di Go utilizzata per creare il programma, l'hardware e il carico sul sistema.
TL; DR
- Più chiamate simultanee alla
fmt.Fprint*scrittura sullo stesso valore "writer" rimandano la loro concorrenza all'implementazione (tipo) del "writer". - È impossibile avere una gara di dati con oggetti "simili a file" forniti da Go stdlib nella configurazione che hai presentato nella tua domanda.
- Il vero problema non sarà con le gare di dati a livello di programma Go, ma con l'accesso simultaneo a una singola risorsa che avviene a livello del sistema operativo. E lì, non parliamo (di solito) di gare di dati perché i sistemi operativi comuni Go supporta espongono cose su cui si può "scrivere" come astrazioni, dove una vera corsa di dati potrebbe indicare un bug nel kernel o nel driver (e il Il rilevatore di gara di Go non sarà comunque in grado di rilevarlo poiché quella memoria non sarebbe di proprietà del runtime di Go che alimenta il processo).
Fondamentalmente, nel tuo caso, se hai bisogno di essere sicuro che i dati prodotti da una particolare chiamata a fmt.Fprint*escano come un singolo pezzo contiguo all'effettivo ricevitore di dati fornito dal sistema operativo, devi serializzare queste chiamate poiché il fmtpacchetto non fornisce alcuna garanzia in merito il numero di chiamate al Write"writer" fornito per le funzioni che esporta.
La serializzazione può essere esterna (esplicita, cioè "prendi un blocco, chiama fmt.Fprint*, rilascia il blocco") o interna - inserendo il os.Stdoutin un tipo personalizzato che gestisca un blocco e usandolo). E già che ci siamo, il logpacchetto fa proprio questo, e può essere utilizzato immediatamente poiché i "logger" forniti, compreso quello predefinito, consentono di inibire l'output di "intestazioni di registro" (come il timestamp e il nome del file).