I sostenitori di FP hanno affermato che la concorrenza è facile perché il loro paradigma evita lo stato mutevole. Non capisco
Volevo rispondere a questa domanda generale come qualcuno che è un neofita funzionale ma è stato all'altezza dei miei occhi negli effetti collaterali nel corso degli anni e vorrei mitigarli, per tutti i tipi di motivi, incluso più facile (o specificamente "più sicuro, concorrenza ") soggetta a errori". Quando guardo i miei colleghi funzionali e quello che stanno facendo, l'erba sembra un po 'più verde e ha un profumo più gradevole, almeno in questo senso.
Algoritmi seriali
Detto questo, sul tuo esempio specifico, se il tuo problema è di natura seriale e B non può essere eseguito fino a quando A non è finito, concettualmente non puoi eseguire A e B in parallelo, qualunque cosa accada. Devi trovare un modo per spezzare la dipendenza dell'ordine come nella tua risposta basandoti su mosse parallele usando il vecchio stato del gioco, oppure usare una struttura di dati che consenta a parti di essa di essere modificate in modo indipendente per eliminare la dipendenza dell'ordine come proposto nelle altre risposte o qualcosa del genere. Ma ci sono sicuramente una serie di problemi di progettazione concettuale come questo in cui non puoi necessariamente semplicemente multithreading tutto così facilmente perché le cose sono immutabili. Alcune cose saranno di natura seriale fino a quando non troverai un modo intelligente per interrompere la dipendenza dell'ordine, se possibile.
Concorrenza più facile
Detto questo, ci sono molti casi in cui non riusciamo a parallelizzare i programmi che comportano effetti collaterali in luoghi che potrebbero potenzialmente migliorare significativamente le prestazioni semplicemente a causa della possibilità che potrebbe non essere thread-safe. Uno dei casi in cui l'eliminazione dello stato mutabile (o più specificamente, gli effetti collaterali esterni) aiuta molto come vedo è che trasforma "può essere o meno sicuro per i thread" in "sicuramente sicuro per i thread" .
Per rendere tale affermazione un po 'più concreta, considera che ti do il compito di implementare una funzione di ordinamento in C che accetta un comparatore e lo usa per ordinare una matrice di elementi. È pensato per essere abbastanza generalizzato, ma ti darò una semplice ipotesi che verrà utilizzato contro input di tale scala (milioni di elementi o più) che senza dubbio sarà utile utilizzare sempre un'implementazione multithread. Puoi eseguire il multithreading della tua funzione di ordinamento?
Il problema è che non puoi perché i comparatori possono chiamare la tua funzione di ordinamentocausare effetti collaterali a meno che non si sappia come sono implementati (o quantomeno documentati) per tutti i possibili casi che è un po 'impossibile senza degeneralizzare la funzione. Un comparatore potrebbe fare qualcosa di disgustoso come modificare una variabile globale all'interno in modo non atomico. Il 99,9999% dei comparatori potrebbe non farlo, ma non possiamo ancora eseguire il multithreading di questa funzione generalizzata semplicemente a causa dello 0,00001% dei casi che potrebbero causare effetti collaterali. Di conseguenza, potrebbe essere necessario offrire una funzione di ordinamento a thread singolo e multithread e passare la responsabilità ai programmatori che la utilizzano per decidere quale utilizzare in base alla sicurezza del thread. E le persone potrebbero ancora utilizzare la versione a thread singolo e perdere opportunità di multithread perché potrebbero anche non essere sicuri che il comparatore sia thread-safe,
C'è un sacco di potere cerebrale che può essere coinvolto solo nella razionalizzazione della sicurezza del filo delle cose senza lanciare blocchi ovunque che può andare via se avessimo solo garanzie concrete che le funzioni non causeranno effetti collaterali per ora e per il futuro. E c'è la paura: la paura pratica, perché chiunque abbia dovuto eseguire il debug di una condizione di gara un numero eccessivo di volte probabilmente sarebbe titubante nel multithreading di qualcosa che non può essere sicuro al 110% di essere thread-safe e rimarrà tale. Anche per i più paranoici (di cui probabilmente sono al limite), la pura funzione fornisce quel senso di sollievo e fiducia che possiamo tranquillamente chiamare in parallelo.
E questo è uno dei casi principali in cui lo vedo così vantaggioso se puoi ottenere una dura garanzia che tali funzioni sono thread-safe che ottieni con linguaggi funzionali puri. L'altro è che i linguaggi funzionali spesso promuovono la creazione di funzioni prive di effetti collaterali in primo luogo. Ad esempio, potrebbero fornire strutture di dati persistenti in cui è abbastanza abbastanza efficiente immettere una struttura di dati di grandi dimensioni e quindi produrne una nuova con solo una piccola parte di essa modificata dall'originale senza toccare l'originale. Coloro che lavorano senza tali strutture dati potrebbero volerli modificare direttamente e perdere un po 'di sicurezza dei thread lungo il percorso.
Effetti collaterali
Detto questo, non sono d'accordo con una parte con tutto il rispetto per i miei amici funzionali (che penso siano super fighi):
[...] perché il loro paradigma evita lo stato mutabile.
Non è necessariamente l'immutabilità che rende la concorrenza così pratica come la vedo io. Sono funzioni che evitano di causare effetti collaterali. Se una funzione immette un array per ordinare, lo copia e quindi muta la copia per ordinare il suo contenuto e produce la copia, è ancora sicura come un thread che lavora con un tipo di array immutabile anche se si passa lo stesso input array ad esso da più thread. Quindi penso che ci sia ancora posto per i tipi mutabili nella creazione di codice molto favorevole alla concorrenza, per così dire, anche se ci sono molti vantaggi aggiuntivi per i tipi immutabili, tra cui strutture di dati persistenti che non uso tanto per le loro proprietà immutabili ma per eliminare il costo di dover copiare in profondità tutto per creare funzioni prive di effetti collaterali.
E c'è spesso il sovraccarico di rendere le funzioni prive di effetti collaterali sotto forma di shuffle e copia di alcuni dati aggiuntivi, forse un livello extra di indiretta, e forse alcuni GC su parti di una struttura di dati persistente, ma guardo uno dei miei amici che ha una macchina a 32 core e sto pensando che lo scambio valga probabilmente la pena se possiamo fare più cose in modo più sicuro in parallelo.