Esempi concreti
Vorrei aggiungere alcuni esempi del mondo reale e collegarli al mondo dell'ingegneria del software. Innanzitutto, considera qualcosa che spero corrisponda alla tua definizione intuitiva di "sincrono": il lampeggiamento di lucciole , in alcune circostanze. In secondo luogo, considerare staffetta 4x100 femminile gara . Terzo, considera quel vecchio trofeo dei film militari: "Uomini, sincronizzate i vostri orologi!"
Ora, pensiamo a cosa sta succedendo. Cominciamo osservando che tutte queste cose sono processi o entità estese nel tempo . Non ha senso dire che una ciotola è "sincrona" e che il rock è "asincrono". In secondo luogo, ci vogliono due per il tango . Non si può dire che "un corridore è sincronizzato". Sincronizzare con cosa? Infine, affinché due processi facciano qualcosa contemporaneamente, a meno che non abbiano già la stessa identica frequenza e fase, uno o entrambi devono attendere .
Analisi
Quando la definizione del dizionario dice che due entità sincronizzate "si presentano o esistono allo stesso tempo", si allinea molto bene al concetto di luce delle lucciole. Sfortunatamente, dire che la luce è "sincronizzata" è un modo sciatto di dire che i processi di illuminazione della lucciola sono sincronizzati.
Quindi, come può un gruppo di lucciole, che presumibilmente non hanno Apple SmartWatch e NTP per guidarli, riuscire a far lampeggiare le loro estremità posteriori allo stesso tempo? Bene, è abbastanza facile se hanno un mezzo per impostare un tempo coerente e possono fare piccoli aggiustamenti. Lampeggiano semplicemente e se più persone lampeggiano subito dopo di loro, rallentano (aumentano il ritardo), mentre se più flash subito prima di loro, accelerano (diminuiscono il ritardo). Quindi possono usare un semplice processo di feedback per arrivare essenzialmente allo stesso tempo e fase. L'importante osservazione qui è notare che raggiungono la sincronia aspettando il momento giusto per lampeggiare .
La gara 4x100 è interessante perché vedete entrambe le forme di tempistica del processo in azione: i corridori all'interno di una squadra sono sincronizzati, mentre i corridori di squadre diverse sono "asincroni". Il secondo corridore nel relè deve attendere fino a quando il primo corridore entra nella zona di trasferimento . Il passaggio è un evento sincrono tra quei due corridori. Tuttavia, i corridori in corsie diverse non si preoccupano di ciò che sta accadendo in un'altra corsia , e sicuramente non rallentano e eseguono le loro mani in sincronia. Ogni corsia di corridori è asincrona l'una rispetto all'altra. Ancora una volta, vediamo che la sincronizzazione implica l'attesa, mentre l'asincronia no.
Infine, i soldati di una compagnia (plotone, squadra dei pompieri, ecc.) Devono sincronizzare i loro orologi in modo da poter attaccare il nemico allo stesso tempo . È possibile che alcuni soldati arrivino alle loro posizioni prima di altri o abbiano l'opportunità di sparare prima sul nemico. Ma un attacco simultaneo è generalmente più efficace di un attacco casuale a causa dell'elemento sorpresa. Quindi, per raggiungere la sincronia, molti soldati devono attendere il tempo stabilito per agire.
Definizione delle funzionalità
Perché questa enfasi sull'attesa? Bene, è perché aspettare è la caratteristica che distingue i processi sincroni da quelli asincroni. Se hai due processi di cui non sai nulla, per impostazione predefinita dovresti supporre che siano asincroni. Ad esempio, molto probabilmente una consegna del pacco e un'ambulanza che guidano non sono sincronizzate. Al fine di dimostrare che due processi sono, infatti, sincronizzato, è necessario trovare un momento molto speciale nel tempo: il punto di sincronizzazione .
Un corriere che consegna un pacco e un'ambulanza che porta qualcuno in ospedale generalmente non condividono alcun punto nel tempo che identifichiamo come "punto di sincronizzazione". D'altra parte, le lucciole che lampeggiano all'unisono hanno un punto di sincronizzazione ogni volta che lampeggiano, i corridori di staffetta hanno un punto di sincronizzazione ogni volta che passano il testimone e i soldati hanno un punto di sincronizzazione quando lanciano il loro attacco. Se riesci a identificare uno o più punti di sincronizzazione, i processi sono sincronizzati . Questo dovrebbe essere facile da capire, perché "syn-" è un prefisso greco che significa "con" o "insieme", e "chrono" è la radice greca per "tempo". "Sincronizzato" significa letteralmente "allo stesso tempo",
confini
Si noti che la "sincronizzazione" non si applica necessariamente all'intera durata di uno o entrambi i processi. Direi che si applica solo al "tempo di attesa fino ai punti di sincronizzazione inclusi". Pertanto, due processi possono operare in modo asincrono fino a quando non raggiungono uno stato in cui devono comunicare, quindi si sincronizzano, si scambiano informazioni e successivamente continuano in modo asincrono. Un semplice esempio è incontrare qualcuno per un caffè. Ovviamente, l'incontro è un punto di sincronizzazione (o molti, piuttosto), e il fatto che due persone arrivino a quel punto dimostra la sincronia. Tuttavia, non diremmo che, poiché due persone si sono incontrate per un caffè, quelle due vite umanesono "sincronizzati". Può darsi che sia stato l'unico istante nella loro vita che hanno incontrato, e tutto il resto che fanno è altrimenti indipendente.
Non è nemmeno il caso che incontri accidentali dimostrino la sincronia. Se due sconosciuti si incrociano per strada, il fatto che si trovino in un determinato luogo in qualche momento non dimostra sincronia. Né il fatto che una persona sia seduta su una panchina ad aspettare l'autobus, mentre un'altra passa di soppiatto. I processi sono sincroni solo quando si incontrano per uno scopo .
Connessione software
Ora, pensiamo a un compito fondamentale nel software: leggere da un file. Come probabilmente saprai, l'archiviazione di massa è in genere da migliaia a milioni di volte più lenta della cache o della memoria principale. Per questo motivo, i sistemi operativi e le librerie dei linguaggi di programmazione generalmente offrono operazioni di I / O sincrone e asincrone. Ora, anche se il tuo programma ha un solo thread, dovresti pensare al sistema operativo come un "processo separato" ai fini di questa discussione.
Sync
Quando si effettua una "lettura I / O sincrona", il thread deve attendere che i dati siano disponibili, a quel punto continua. Questo è molto simile a un corridore a staffetta che consegna il testimone al prossimo corridore, ma immagina invece un relè con solo due corridori che percorrono tutto il tracciato, e anche il secondo corridore restituisce il primo.
In questo caso, il thread del programma e il processo di I / O del sistema operativo non "stanno accadendo (agendo) contemporaneamente", quindi sembra strano affermare che questi processi sono "sincronizzati". Ma questo è il modo sbagliato di vederlo! È come dire: "I corridori di una squadra staffetta non corrono contemporaneamente, quindi non sono sincronizzati". In effetti, entrambe le affermazioni sono sbagliate! I corridori di una squadra di staffette lo fanno e devono correre allo stesso tempo, ma solo in un momento molto specifico: la consegna del testimone. In effetti, è solo questo momento speciale durante la gara che ci convince che le squadre di staffette sono sincronizzate per cominciare! Se consideriamo la richiesta e la risposta I / O come "il testimone",
D'altra parte, se pensiamo a qualcosa come l'analisi agli elementi finiti su un supercomputer, vediamo che migliaia di processi devono funzionare in blocco per aggiornare un enorme stato globale. Anche se alcuni nodi completano il loro lavoro per un determinato intervallo di tempo prima di altri, devono tutti attendere il completamento del passaggio di tempo perché i risultati si propagano ai vicini attraverso lo spazio. Questo tipo di sincronizzazione è come le lucciole: tutti gli attori svolgono lo stesso tipo di attività.
Varietà di processo
Per questo motivo, possiamo inventare alcuni termini per aiutarci a capire che ci sono tre tipi di cose: "sincronia omogenea", "sincronia eterogenea" e "sincronia sequenziale". Quindi, quando gli attori svolgono simultaneamente lo stesso compito (FEA, lucciole), sono "omogenei". Quando eseguono compiti diversi contemporaneamente (soldati che corrono contro gattonare contro il nuoto verso le loro destinazioni, fisica contro suono contro thread di intelligenza artificiale in un gioco), sono "eterogenei". Quando eseguono i compiti uno alla volta, sono "sequenziali" (relè, blocchi I / O). Possono sembrare molto diversi, ma condividono una proprietà essenziale: tutti i tipi di attori si esibiscono in attesa di assicurarsi che tutti arrivino al punto di sincronizzazione contemporaneamente. tra punti di sincronizzazione o "eseguire la stessa azione" è irrilevante per la proprietà della sincronicità.
Le pipeline di rendering in una GPU sono sincrone perché devono tutti finire il frame insieme e iniziare un nuovo frame insieme. Sono omogenei perché svolgono gli stessi tipi di lavoro e sono tutti attivi insieme. Ma il loop di gioco principale di un server e i thread di I / O bloccanti che elaborano l'input remoto sono eterogenei perché svolgono tipi di lavoro molto diversi e alcuni dei thread di I / O non faranno nulla, perché non tutti vengono utilizzate le connessioni. Anche così, sono sincronizzati, perché devono condividere lo stato atomicamente (un giocatore non deve vedere un aggiornamento parziale del mondo di gioco, né il server deve vedere solo un frammento dell'input del giocatore).
Async
Consideriamo ora una "lettura I / O asincrona". Quando il programma invia una richiesta al sistema operativo per leggere un po 'di dati dalla memoria, la chiamata ritorna immediatamente . Ignoriamo i callback e concentriamoci sul polling. In generale, il momento in cui i dati sono disponibili per il tuo programma non corrispondono ad alcun momento speciale per quanto riguarda il thread del tuo programma. Se il tuo programma non sta aspettando esplicitamente i dati, il thread non saprà nemmeno esattamente quando si verificherà quel momento. Scoprirà solo che i dati sono in attesa al successivo controllo.
Non esiste un orario di incontro speciale in cui il sistema operativo e il thread del programma accettano di consegnare i dati. Sono come due navi che passano di notte. L'asincronia è caratterizzata da questa assenza di attesa. Ovviamente, il thread del programma finirà spesso per attendere l'operazione di I / O, ma non è necessario. Può tranquillamente continuare a fare altri calcoli mentre si sta verificando l'I / O fetch e controllare solo in seguito quando ha un momento da perdere. Naturalmente, una volta terminato il recupero dei dati, il sistema operativo non si ferma nemmeno in attesa. Mette semplicemente i dati in un posto conveniente e continua la sua attività. In questo caso, è come se il programma consegnasse il testimone al sistema operativo, e il sistema operativo riapparisse in seguito, lascia cadere il testimone a terra insieme ai dati e esce dalla pista. Il programma potrebbe essere in attesa o meno di ricevere la consegna.
Parallelismo
Quando contrassegniamo una funzione come "asincrona" nel software, spesso significa che vogliamo il parallelismo . Ma ricorda che il parallelismo non implica la sincronia . Le lucciole sono un buon esempio, perché anche loro hanno mostrato un comportamento sincrono e asincrono. Mentre la maggior parte delle mosche lampeggiava all'unisono, molte erano ovviamente stonate con il resto del gruppo e lampeggiavano in modo più casuale. Le mosche potrebbero aver agito simultaneamente , ma non erano tutte sincronizzate .
Ora, quando contrassegniamo un codice come "asincrono", sembra divertente, perché implica che il resto del codice non così contrassegnato sia "sincronizzazione". Che cosa vuol dire, anche? Non abbiamo insistito sul fatto che la "sincronizzazione" richiedesse due al tango? Ma cosa succede se stiamo parlando dell'esecuzione del codice in un singolo thread? In questo caso, dobbiamo fare un passo indietro e pensare a un programma come una sequenza di stati e transizioni tra quegli stati. Un'istruzione in un programma provoca una transizione di stato. Possiamo considerarlo come un "micro-processo" che inizia e termina con l'affermazione. I punti di sequenza definiti dal linguaggio sono, in effetti, i punti di sincronizzazione di questi "micro-processi". E quindi, possiamo visualizzare un thread singolo,
L'integrità del linguaggio di programmazione garantisce che gli aggiornamenti di stato non interferiscano tra le istruzioni e i punti di sequenza definiscono i confini attraverso i quali al compilatore non è consentito effettuare ottimizzazioni osservabili. Ad esempio, l'ordine di valutazione delle espressioni all'interno di un'istruzione potrebbe essere indefinito o non specificato, dando al compilatore la libertà di ottimizzare l'affermazione in vari modi. Ma quando inizia la prossima affermazione, il programma dovrebbe essere in uno stato ben definito, se il PL stesso è valido.
Ormai dovrebbe essere chiaro cosa intendiamo per "asincrono". Significa esattamente che il contratto implicito di sincronia all'interno di un blocco di codice è esentato per il blocco asincrono. È consentito aggiornare lo stato del programma in modo indipendente, senza le garanzie di sicurezza normalmente implicate nel modello di calcolo sequenziale (sostanzialmente coerente, sincrono). Naturalmente, ciò significa che dobbiamo prestare particolare attenzione a non distruggere lo stato del programma con incoerenza. Questo di solito significa che introduciamo una sincronia limitata ed esplicita per coordinarci con il blocco asincrono. Nota che questo significa che il blocco asincrono può essere sia asincrono che sincrono in momenti diversi! Ma ricordando che la sincronizzazione indica semplicemente l'esistenza di un punto di sincronizzazione, non dovremmo avere problemi ad accettare questa nozione.