LinkedBlockingQueue vs ConcurrentLinkedQueue


112

La mia domanda si riferisce a questa domanda posta in precedenza. Nelle situazioni in cui sto utilizzando una coda per la comunicazione tra i thread del produttore e del consumatore, le persone generalmente consigliano di utilizzare LinkedBlockingQueueo ConcurrentLinkedQueue?

Quali sono i vantaggi / svantaggi dell'utilizzo dell'uno rispetto all'altro?

La differenza principale che posso vedere dal punto di vista dell'API è che a LinkedBlockingQueuepuò essere facoltativamente delimitato.

Risposte:


110

Per un thread produttore / consumatore, non sono sicuro che ConcurrentLinkedQueuesia nemmeno un'opzione ragionevole - non implementa BlockingQueue, che è l'interfaccia fondamentale per le code produttore / consumatore IMO. Dovresti chiamare poll(), aspettare un po 'se non hai trovato nulla, quindi eseguire di nuovo il sondaggio ecc ... portando a ritardi quando arriva un nuovo elemento e inefficienze quando è vuoto (a causa del risveglio inutile dalle pause) .

Dai documenti per BlockingQueue:

BlockingQueue le implementazioni sono progettate per essere utilizzate principalmente per le code produttore-consumatore

So che non dice rigorosamente che solo le code di blocco dovrebbero essere utilizzate per le code produttore-consumatore, ma anche così ...


4
Grazie Jon - non l'avevo notato. Quindi dove / perché dovresti usare ConcurrentLinkedQueue?
Adamski

27
Quando è necessario accedere alla coda da molti thread, ma non è necessario "attendere" su di essa.
Jon Skeet

2
A ConcurrentLinkedQueueè utile anche se il tuo thread sta controllando più code. Ad esempio, in un server multi-tenant. Supponendo che per motivi di isolamento non si utilizzi una singola coda di blocco e un discriminatore tenant.
LateralFractal

il tuo caso è vero solo se usiamo una coda limitata , in coda illimitatatake() e put()consuma semplicemente risorse extra (intervalli di sincronizzazione) rispetto a ConcurrentLinkedQueue . anche se è il caso di utilizzare code limitate per scenari produttore-consumatore
amarnath harish

@Adamski IMO, ConcurrentLinkedQueue è solo una lista collegata da utilizzare in un ambiente multi thread. La migliore analogia per questo sarebbe ConcurrentHashMap e HashMap.
Nishit

69

Questa domanda merita una risposta migliore.

Java ConcurrentLinkedQueueè basato sul famoso algoritmo di Maged M. Michael e Michael L. Scott per il blocco senza blocco code .

"Non blocco" come termine qui per una risorsa contesa (la nostra coda) significa che indipendentemente da ciò che fa lo scheduler della piattaforma, come interrompere un thread, o se il thread in questione è semplicemente troppo lento, altri thread si contendono la stessa risorsa potrà ancora progredire. Se è coinvolto un blocco, ad esempio, il thread che lo contiene potrebbe essere interrotto e tutti i thread in attesa di quel blocco verranno bloccati. I blocchi intrinseci (la synchronizedparola chiave) in Java possono anche comportare una grave penalità per le prestazioni, come quando blocco polarizzatoè coinvolto e hai una contesa, o dopo che la VM decide di "gonfiare" il blocco dopo un periodo di tolleranza di rotazione e bloccare i thread contendenti ... motivo per cui in molti contesti (scenari di contesa bassa / media), fare confronti e -set su riferimenti atomici possono essere molto più efficienti e questo è esattamente ciò che stanno facendo molte strutture di dati non bloccanti.

Java ConcurrentLinkedQueuenon è solo non bloccante, ma ha la fantastica proprietà che il produttore non può competere con il consumatore. In uno scenario singolo produttore / singolo consumatore (SPSC), ciò significa davvero che non ci saranno contese di cui parlare. In uno scenario produttore multiplo / singolo consumatore, il consumatore non contenderà i produttori. Questa coda ha conflitti quando più produttori provano a farlooffer() , ma questa è concorrenza per definizione. È fondamentalmente una coda non bloccante per scopi generali ed efficiente.

Per quanto riguarda il fatto che non sia un BlockingQueue, beh, bloccare un thread in attesa in una coda è un modo spaventosamente terribile di progettare sistemi concorrenti. Non farlo. Se non riesci a capire come utilizzare un ConcurrentLinkedQueuein uno scenario di consumatore / produttore, passa semplicemente ad astrazioni di livello superiore, come un buon framework per attori.


8
Secondo il tuo ultimo paragrafo, perché dici che aspettare in coda è un modo terribile di progettare sistemi concorrenti? Se abbiamo un gruppo di thread con 10 thread che mangiano attività da una coda di attività, cosa c'è di sbagliato nel bloccare quando la coda di attività ha meno di 10 attività?
Pacerier

11
@AlexandruNedelcu Non puoi fare un'affermazione radicale come "spaventosamente terribile" dove molto spesso i framework degli attori che dici di usare usano threadpool che a loro volta BlockingQueue è. Se hai bisogno di un sistema altamente reattivo e sai come affrontare la contropressione (qualcosa che mitiga le code di blocco), il non blocco è chiaramente superiore. Ma ... spesso le volte che bloccano l'IO e le code di blocco possono eseguire operazioni non bloccanti, in particolare se si hanno attività a esecuzione prolungata che sono legate all'IO e non possono essere divise e conquistate.
Adam Gent,

1
@AdamGent - I framework degli attori hanno l'implementazione delle cassette postali basata sul blocco delle code, ma questo è un bug secondo me, perché il blocco non funziona oltre i limiti asincroni e quindi funziona solo nelle demo. Per me questo è stato fonte di frustrazione, poiché ad esempio l'idea di Akka di gestire l'overflow è di bloccare, invece di far cadere i messaggi, fino alla versione 2.4, cioè, che non è ancora disponibile. Detto questo, non credo che ci siano casi d'uso per i quali il blocco delle code può essere superiore. Stai anche fondendo due cose che non dovrebbero essere fuse. Non ho parlato del blocco dell'I / O.
Alexandru Nedelcu

1
@AlexandruNedelcu mentre sono generalmente d'accordo con te sulla contropressione, devo ancora vedere un sistema "senza blocco" dall'alto verso il basso. Da qualche parte in una pila tecnologica sia che i suoi Node.js, Erlang, Golang, stiano usando una sorta di strategia di attesa, sia che si tratti di una coda di blocco (blocchi) o di un CAS che fa girare il suo blocco e in alcuni casi una strategia di blocco tradizionale è più veloce. È molto difficile non avere blocchi a causa della coerenza e questo è particolarmente importante con il blocco di io e gli scheduler che sono ~ Producer / Consumer. ForkJoinPool funziona con attività in esecuzione brevi e ha ancora blocchi rotanti CAS.
Adam Gent

1
@AlexandruNedelcu Immagino che se puoi mostrarmi come puoi usare un ConcurrentLinkedQueue (che non è limitato btw da qui il mio debole argomento di contropressione) per il modello Produttore / Consumatore che è un modello necessario per gli scheduler e il threadpool, penso che mi arrenderò e lo ammetto BlockingQueue's non dovrebbe mai essere usato (e non puoi imbrogliare e delegare a qualcos'altro che fa la pianificazione, cioè akka poiché questo a sua volta farà il blocco / attesa in quanto è un produttore / consumatore).
Adam Gent

33

LinkedBlockingQueueblocca il consumatore o il produttore quando la coda è vuota o piena e il rispettivo thread consumatore / produttore viene messo in sleep. Ma questa funzionalità di blocco ha un costo: ogni operazione put o take è contesa tra produttori o consumatori (se molti), quindi in scenari con molti produttori / consumatori l'operazione potrebbe essere più lenta.

ConcurrentLinkedQueuenon utilizza i lock, ma CAS , nelle sue operazioni put / take, riducendo potenzialmente la contesa con molti thread producer e consumer. Ma essendo una struttura dati "senza attesa", ConcurrentLinkedQueuenon si bloccherà quando è vuota, il che significa che il consumatore dovrà occuparsi dei valori take()restituiti nullda "attesa occupata", ad esempio, con il thread del consumatore che consuma la CPU.

Quindi quale è "migliore" dipende dal numero di thread di consumatori, dalla velocità che consumano / producono, ecc. È necessario un benchmark per ogni scenario.

Un caso d'uso particolare in cui ConcurrentLinkedQueueè chiaramente migliore è quando i produttori producono prima qualcosa e finiscono il loro lavoro mettendo il lavoro in coda e solo dopo che i consumatori iniziano a consumare, sapendo che avranno finito quando la coda è vuota. (qui non c'è concorrenza tra produttore-consumatore ma solo tra produttore-produttore e consumatore-consumatore)


un dubbio qui. Come hai detto, il consumatore attende quando la coda è vuota ... quanto tempo attende. Chi lo avviserà di non aspettare?
Brinal

@brindal L'unico modo per aspettare, che io sappia, è in un ciclo. Che è un problema significativo a cui non è stata prestata molta attenzione nelle risposte qui. L'esecuzione di un ciclo in attesa di dati utilizza molto tempo del processore. Lo saprai quando i tuoi fan inizieranno a girare su di giri. L'unico rimedio è mettere un sonno nel ciclo. Quindi è un problema in un sistema con flusso di dati incoerente. Forse fraintendo la risposta di AlexandruNedelcu, ma un sistema operativo stesso è un sistema concorrente, che sarebbe estremamente inefficiente se fosse pieno di loop di eventi non bloccanti.
orodbhen

va bene, ma se unbounded blockingqueueviene utilizzato sarebbe meglio del concorrente basato su CASConcurrentLinkedQueue
amarnath harish

@orodbhen Anche mettere un sonno non eliminerebbe gli sprechi. Il sistema operativo deve fare molto lavoro per far uscire un thread dallo stato di stop, programmarlo ed eseguirlo. Se i messaggi non sono ancora disponibili, il lavoro svolto dal tuo sistema operativo va sprecato. Consiglierei che è meglio usare BlockingQueue, poiché è stato progettato specificamente per il problema produttore-consumatore.
Nishit

in realtà, sono molto interessato alla parte "consumo / produzione", quindi quale è meglio se la tariffa aumenta?
workplaylifecycle

0

Un'altra soluzione (che non scala bene) sono i canali rendezvous: java.util.concurrent SynchronousQueue


0

Se la tua coda non è espandibile e contiene un solo thread produttore / consumatore. È possibile utilizzare la coda senza blocco (non è necessario bloccare l'accesso ai dati).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.