Concorrenza vs Parallelismo

Sia la concorrenza che il parallelismo vengono utilizzati in relazione ai programmi multithread, ma c'è molta confusione sulla somiglianza e la differenza tra loro. La grande domanda a questo proposito: è il parallelismo di concorrenza o no? Sebbene entrambi i termini appaiano abbastanza simili ma la risposta alla domanda precedente è NO, la concorrenza e il parallelismo non sono gli stessi. Ora, se non sono uguali, qual è la differenza fondamentale tra loro?

In termini semplici, la concorrenza si occupa della gestione dell'accesso allo stato condiviso da thread diversi e, d'altra parte, il parallelismo si occupa dell'utilizzo di più CPU o dei suoi core per migliorare le prestazioni dell'hardware.

Concorrenza in dettaglio

La concorrenza è quando due attività si sovrappongono durante l'esecuzione. Potrebbe essere una situazione in cui un'applicazione sta procedendo su più di un'attività contemporaneamente. Possiamo capirlo schematicamente; più attività stanno progredendo contemporaneamente, come segue:

Livelli di concorrenza

In questa sezione, discuteremo i tre importanti livelli di concorrenza in termini di programmazione:

Concorrenza di basso livello

In questo livello di concorrenza, vi è un uso esplicito di operazioni atomiche. Non è possibile utilizzare questo tipo di concorrenza per la creazione di applicazioni, poiché è molto soggetta a errori e difficile da eseguire il debug. Anche Python non supporta questo tipo di concorrenza.

Concorrenza di medio livello

In questa concorrenza, non è possibile utilizzare operazioni atomiche esplicite. Usa i blocchi espliciti. Python e altri linguaggi di programmazione supportano questo tipo di concorrenza. La maggior parte dei programmatori di applicazioni utilizza questa concorrenza.

Concorrenza di alto livello

In questa concorrenza, non vengono utilizzate né operazioni atomiche esplicite né blocchi espliciti. Python haconcurrent.futures modulo per supportare questo tipo di concorrenza.

Proprietà dei sistemi concorrenti

Perché un programma o un sistema concorrente sia corretto, alcune proprietà devono essere soddisfatte da esso. Le proprietà relative alla chiusura del sistema sono le seguenti:

Proprietà di correttezza

La proprietà correttezza significa che il programma o il sistema deve fornire la risposta corretta desiderata. Per semplificare, possiamo dire che il sistema deve mappare correttamente lo stato del programma iniziale allo stato finale.

Proprietà di sicurezza

La proprietà di sicurezza significa che il programma o il sistema deve rimanere in un file “good” o “safe” dichiara e non fa mai nulla “bad”.

Proprietà di vitalità

Questa proprietà significa che un programma o un sistema deve “make progress” e raggiungerebbe uno stato desiderabile.

Attori di sistemi concorrenti

Questa è una proprietà comune del sistema simultaneo in cui possono esserci più processi e thread, che vengono eseguiti contemporaneamente per avanzare nelle proprie attività. Questi processi e thread sono chiamati attori del sistema concorrente.

Risorse di sistemi concorrenti

Gli attori devono utilizzare le risorse come memoria, disco, stampante, ecc. Per svolgere i loro compiti.

Un certo insieme di regole

Ogni sistema concorrente deve possedere una serie di regole per definire il tipo di compiti che devono essere eseguiti dagli attori e la tempistica per ciascuno. Le attività potrebbero essere l'acquisizione di blocchi, la condivisione della memoria, la modifica dello stato, ecc.

Barriere di sistemi concorrenti

Durante l'implementazione di sistemi concorrenti, il programmatore deve prendere in considerazione i seguenti due importanti problemi, che possono essere le barriere dei sistemi concorrenti:

Condivisione dei dati

Un problema importante durante l'implementazione dei sistemi simultanei è la condivisione dei dati tra più thread o processi. In realtà, il programmatore deve assicurarsi che i lock proteggano i dati condivisi in modo che tutti gli accessi ad essi siano serializzati e solo un thread o processo possa accedere ai dati condivisi alla volta. Nel caso in cui più thread o processi stiano tentando di accedere agli stessi dati condivisi, non tutti, ma almeno uno di essi verrebbe bloccato e rimarrebbe inattivo. In altre parole, possiamo dire che saremmo in grado di utilizzare solo un processo o thread alla volta quando il blocco è in vigore. Ci possono essere alcune semplici soluzioni per rimuovere le barriere sopra menzionate:

Restrizione alla condivisione dei dati

La soluzione più semplice è non condividere dati mutabili. In questo caso, non è necessario utilizzare il blocco esplicito e la barriera di concorrenza dovuta ai dati reciproci verrebbe risolta.

Assistenza per la struttura dei dati

Molte volte i processi simultanei devono accedere agli stessi dati contemporaneamente. Un'altra soluzione, rispetto all'utilizzo di blocchi espliciti, consiste nell'usare una struttura dati che supporti l'accesso simultaneo. Ad esempio, possiamo usare ilqueuemodulo, che fornisce code thread-safe. Possiamo anche usaremultiprocessing.JoinableQueue classi per la concorrenza basata su multiprocessing.

Trasferimento dati immutabile

A volte, la struttura dei dati che stiamo utilizzando, ad esempio la coda di concorrenza, non è adatta, quindi possiamo passare i dati immutabili senza bloccarli.

Trasferimento dati mutevole

In continuazione della soluzione di cui sopra, supponiamo che se è richiesto di passare solo dati mutabili, invece di dati immutabili, allora possiamo passare dati mutabili di sola lettura.

Condivisione delle risorse di I / O

Un altro problema importante nell'implementazione di sistemi simultanei è l'uso delle risorse di I / O da parte di thread o processi. Il problema sorge quando un thread o un processo utilizza l'I / O per così tanto tempo e l'altro è inattivo. Possiamo vedere questo tipo di barriera mentre lavoriamo con un'applicazione pesante di I / O. Si può comprendere, con l'aiuto di un esempio, la richiesta di pagine dal browser web. È un'applicazione pesante. Qui, se la velocità con cui vengono richiesti i dati è più lenta della velocità con cui vengono consumati, allora abbiamo una barriera I / O nel nostro sistema simultaneo.

Il seguente script Python serve per richiedere una pagina web e ottenere il tempo impiegato dalla nostra rete per ottenere la pagina richiesta -

import urllib.request

import time

ts = time.time()

req = urllib.request.urlopen('http://www.tutorialspoint.com')

pageHtml = req.read()

te = time.time()

print("Page Fetching Time : {} Seconds".format (te-ts))

Dopo aver eseguito lo script precedente, possiamo ottenere il tempo di recupero della pagina come mostrato di seguito.

Produzione

Page Fetching Time: 1.0991398811340332 Seconds

Possiamo vedere che il tempo per recuperare la pagina è più di un secondo. E se volessimo recuperare migliaia di pagine web diverse, puoi capire quanto tempo impiegherebbe la nostra rete.

Cos'è il parallelismo?

Il parallelismo può essere definito come l'arte di suddividere le attività in sottoattività che possono essere elaborate simultaneamente. È l'opposto della concorrenza, come discusso in precedenza, in cui due o più eventi si verificano contemporaneamente. Possiamo capirlo schematicamente; un'attività è suddivisa in una serie di attività secondarie che possono essere elaborate in parallelo, come segue:

Per avere più idea della distinzione tra concorrenza e parallelismo, considera i seguenti punti:

Concorrente ma non parallelo

Un'applicazione può essere simultanea ma non parallela significa che elabora più di un'attività contemporaneamente ma le attività non sono suddivise in sottoattività.

Parallelo ma non simultaneo

Un'applicazione può essere parallela ma non simultanea significa che funziona solo su un'attività alla volta e le attività suddivise in sottoattività possono essere elaborate in parallelo.

Né parallelo né concorrente

Un'applicazione non può essere né parallela né concorrente. Ciò significa che funziona solo su un'attività alla volta e l'attività non viene mai suddivisa in sottoattività.

Sia parallelo che concorrente

Un'applicazione può essere sia parallela che simultanea significa che funziona su più attività contemporaneamente e l'attività è suddivisa in attività secondarie per eseguirle in parallelo.

Necessità del parallelismo

Possiamo ottenere il parallelismo distribuendo le attività secondarie tra diversi core di una singola CPU o tra più computer collegati all'interno di una rete.

Considera i seguenti punti importanti per capire perché è necessario ottenere il parallelismo:

Esecuzione efficiente del codice

Con l'aiuto del parallelismo, possiamo eseguire il nostro codice in modo efficiente. Ci farà risparmiare tempo perché lo stesso codice in alcune parti viene eseguito in parallelo.

Più veloce del calcolo sequenziale

Il calcolo sequenziale è vincolato da fattori fisici e pratici a causa dei quali non è possibile ottenere risultati di calcolo più rapidi. D'altra parte, questo problema viene risolto dal calcolo parallelo e ci fornisce risultati di calcolo più rapidi rispetto al calcolo sequenziale.

Meno tempo di esecuzione

L'elaborazione parallela riduce il tempo di esecuzione del codice del programma.

Se parliamo di esempio di parallelismo nella vita reale, la scheda grafica del nostro computer è l'esempio che mette in evidenza la vera potenza dell'elaborazione parallela perché ha centinaia di singoli core di elaborazione che funzionano in modo indipendente e possono eseguire l'esecuzione allo stesso tempo. Per questo motivo, siamo in grado di eseguire anche applicazioni e giochi di fascia alta.

Comprensione dei processori per l'implementazione

Conosciamo la concorrenza, il parallelismo e la differenza tra loro, ma per quanto riguarda il sistema su cui deve essere implementato. È molto necessario avere la comprensione del sistema, su cui stiamo per implementare, perché ci dà il vantaggio di prendere decisioni informate durante la progettazione del software. Abbiamo i seguenti due tipi di processori:

Processori single-core

I processori single-core sono in grado di eseguire un thread alla volta. Questi processori usanocontext switchingper memorizzare tutte le informazioni necessarie per un thread in un momento specifico e quindi ripristinare le informazioni in un secondo momento. Il meccanismo di cambio di contesto ci aiuta a fare progressi su un numero di thread in un dato secondo e sembra che il sistema stia lavorando su più cose.

I processori single-core hanno molti vantaggi. Questi processori richiedono meno energia e non esiste un protocollo di comunicazione complesso tra più core. D'altra parte, la velocità dei processori single-core è limitata e non è adatta per applicazioni più grandi.

Processori multi-core

I processori multi-core hanno anche più unità di elaborazione indipendenti chiamate cores.

Tali processori non necessitano di un meccanismo di cambio di contesto poiché ogni core contiene tutto ciò di cui ha bisogno per eseguire una sequenza di istruzioni memorizzate.

Fetch-Decode-Execute Cycle

I core dei processori multi-core seguono un ciclo di esecuzione. Questo ciclo è chiamatoFetch-Decode-Executeciclo. Comprende i seguenti passaggi:

Fetch

Questa è la prima fase del ciclo, che prevede il recupero delle istruzioni dalla memoria del programma.

Decodificare

Le istruzioni recuperate di recente verrebbero convertite in una serie di segnali che attiveranno altre parti della CPU.

Eseguire

È il passaggio finale in cui verranno eseguite le istruzioni recuperate e decodificate. Il risultato dell'esecuzione verrà memorizzato in un registro della CPU.

Un vantaggio qui è che l'esecuzione nei processori multi-core è più veloce di quella dei processori single-core. È adatto per applicazioni più grandi. D'altra parte, il protocollo di comunicazione complesso tra più core è un problema. Più core richiedono più potenza rispetto ai processori single-core.