LinkedBlockingQueue ve ConcurrentLinkedQueue


112

Sorum daha önce sorulan bu soru ile ilgili . Üretici ve tüketici iş parçacığı arasındaki iletişim için bir kuyruk kullandığım durumlarda, insanlar genellikle LinkedBlockingQueueveya ConcurrentLinkedQueue?

Birini diğerine göre kullanmanın avantajları / dezavantajları nelerdir?

API perspektifinden görebildiğim temel fark, LinkedBlockingQueuea'nın isteğe bağlı olarak sınırlandırılabilmesidir.

Yanıtlar:


110

Bir üretici / tüketici iş parçacığı için bunun ConcurrentLinkedQueuemakul bir seçenek olduğundan bile emin değilim - BlockingQueueüretici / tüketici kuyrukları IMO için temel arayüz olan uygulanmıyor . Bir poll()şey bulamadıysanız aramanız , biraz beklemeniz ve sonra tekrar anket yapmanız gerekir vb ... yeni bir öğe geldiğinde gecikmelere ve boş olduğunda verimsizliğe neden olur (uykudan gereksiz yere uyanmak nedeniyle) .

BlockingQueue dokümanlarından:

BlockingQueue uygulamalar, öncelikle üretici-tüketici kuyruklarında kullanılmak üzere tasarlanmıştır

Üretici-tüketici kuyrukları için yalnızca engelleme kuyruklarının kullanılması gerektiğini kesin olarak söylemediğini biliyorum , ama öyle bile ...


4
Teşekkürler Jon - Bunu fark etmemiştim. Peki ConcurrentLinkedQueue'yu nerede / neden kullanırsınız?
Adamski

27
Kuyruğa çok sayıda iş parçacığından erişmeniz gerektiğinde, ancak üzerinde "beklemeniz" gerekmez.
Jon Skeet

2
A ConcurrentLinkedQueue, iş parçacığınız birden çok kuyruğu kontrol ediyorsa da kullanışlıdır. Örneğin, çok kiracılı bir sunucuda. İzolasyon nedenlerinden ötürü, bunun yerine tek bir engelleme kuyruğu ve bir kiracı ayırıcı kullanmadığınızı varsayarsak.
LateralFractal

durumunuz yalnızca , sınırsız kuyrukta sınırlı kuyruk kullanırsak ve yalnızca bundan daha fazla kaynak (senkronizasyon aralıkları) tüketirsek doğru kalır . Üretici-tüketici senaryoları için sınırlı kuyrukların kullanılması söz konusu olsa datake()put()ConcurrentLinkedQueue
amarnath harish

@Adamski IMO, ConcurrentLinkedQueue, çok iş parçacıklı bir ortamda kullanılacak bir bağlantılı listedir. Bunun için en iyi benzetme ConcurrentHashMap ve HashMap olacaktır.
Nishit

69

Bu soru daha iyi bir cevabı hak ediyor.

Java en ConcurrentLinkedQueueünlü dayanmaktadır Maged M. Michael ve Michael L. Scott tarafından algoritması için engellenmeyen kilidi serbest sıralar.

Burada bir tartışmalı kaynak (kuyruğumuz) için bir terim olarak "bloke olmama", platformun zamanlayıcısının ne yaptığından bağımsız olarak, bir iş parçacığını kesmek gibi veya söz konusu iş parçacığı çok yavaşsa, aynı kaynak için rekabet eden diğer iş parçacığı anlamına gelir. yine de ilerleyebilecek. Örneğin bir kilit söz konusuysa, kilidi tutan iplik kesilebilir ve bu kilidi bekleyen tüm iplikler engellenebilir. synchronizedJava'daki içsel kilitler ( anahtar kelime) de performans için ciddi bir ceza ile gelebilir - önyargılı kilitlemede olduğu gibiişin içindeyse ve tartışmanız varsa veya VM, bir döndürme yetkisiz kullanım süresinden sonra kilidi "şişirmeye" karar verdikten ve rekabet eden konuları engellemeye karar verdikten sonra ... bu nedenle birçok bağlamda (düşük / orta çekişme senaryoları) karşılaştırma yapmak ve -Atomik referanslar üzerindeki ayarlar çok daha verimli olabilir ve bu, engellemeyen birçok veri yapısının yaptığı şeydir.

Java ConcurrentLinkedQueuesadece engelleyici değildir, aynı zamanda üreticinin tüketiciyle rekabet etmediği harika bir özelliğe sahiptir. Tek bir üretici / tek tüketici senaryosunda (SPSC), bu gerçekten konuşulacak bir çekişme olmayacağı anlamına gelir. Çoklu üretici / tek tüketici senaryosunda, tüketici üreticilerle rekabet etmeyecektir. Bu kuyruk, birden fazla üretici denediğinde çekişmeye neden olur offer(), ancak bu, tanım gereği eşzamanlılıktır. Temelde genel amaçlı ve verimli, engellemeyen bir kuyruktur.

Bir olmama gelince BlockingQueuekuyrukta beklemek için bir iş parçacığı engelleme, iyi, eşzamanlı sistemlerin tasarlanması bir freakishly korkunç bir yoldur. Yapma. ConcurrentLinkedQueueBir tüketici / üretici senaryosunda a'yı nasıl kullanacağınızı çözemezseniz, o zaman iyi bir aktör çerçevesi gibi daha yüksek seviyeli soyutlamalara geçin.


8
Son paragrafınıza göre, neden bir kuyrukta beklemenin eşzamanlı sistemler tasarlamanın korkunç bir yolu olduğunu söylüyorsunuz? Bir görev kuyruğundan görevleri yiyen 10 iş parçacığı olan bir iş parçacığı grubumuz varsa, görev kuyruğunda 10'dan az görev olduğunda engellemenin nesi yanlış?
Pacerier

11
@AlexandruNedelcu Çok sık aktör çerçevelerinin, BlockingQueue'nün kendilerinin de kullandığı threadpool'ları kullandığını söylediğiniz "garip derecede korkunç" gibi kapsamlı bir açıklama yapamazsınız . Son derece reaktif bir sisteme ihtiyacınız varsa ve geri tepme basıncıyla (kuyrukları engelleyen bir şey) nasıl başa çıkılacağını biliyorsanız, engellemeden daha üstündür. Ancak .. çoğu zaman GÇ'yi engelleme ve sıraları engelleme, özellikle GÇ'ye bağlı olan ve ele geçirilemeyen uzun süre çalışan görevleriniz varsa, engellemesizliği aşabilir.
Adam Gent

1
@AdamGent - Aktör çerçeveleri, engelleme kuyruklarına dayalı posta kutuları uygulamasına sahiptir, ancak bu bence bir hata çünkü engelleme eşzamansız sınırlar üzerinde çalışmaz ve bu nedenle yalnızca demolarda çalışır. Benim için bu bir hayal kırıklığı kaynağı oldu, örneğin Akka'nın taşma ile başa çıkma fikri, henüz çıkmamış olan 2.4 sürümüne kadar mesajları bırakmak yerine bloke etmek. Bununla birlikte, engelleme kuyruklarının daha üstün olabileceği kullanım durumları olduğuna inanmıyorum. Ayrıca, karıştırılmaması gereken iki şeyi karıştırıyorsunuz. G / Ç'yi engellemek hakkında konuşmadım.
Alexandru Nedelcu

1
@AlexandruNedelcu geri basınç konusunda genel olarak sizinle aynı fikirdeyken, henüz yukarıdan aşağıya "kilitsiz" bir sistem görmedim. İster Node.js, Erlang, Golang olsun, ister blockingqueue (kilitler) isterse CAS'ın blokajını döndürmesi olsun, teknoloji yığınında bir yerlerde bir çeşit bekleme stratejisi kullanıyor ve bazı durumlarda geleneksel bir kilit stratejisi daha hızlı. Tutarlılık nedeniyle kilit olmaması çok zordur ve bu özellikle ~ Üretici / Tüketici olan io ve zamanlayıcıları bloke ederken önemlidir. ForkJoinPool kısa çalışan görevlerle çalışır ve hala CAS eğirme kilitlerine sahiptir.
Adam Gent

1
@AlexandruNedelcu Zamanlayıcılar ve iş parçacığı paylaşımları için gerekli bir kalıp olan Üretici / Tüketici kalıbı için bir ConcurrentLinkedQueue (ki bu sınırlandırılmamış, dolayısıyla zayıf geri basınç argümanım) nasıl kullanabileceğinizi bana gösterebilirseniz sanırım vereceğimi ve itiraf edeceğimi düşünüyorum. BlockingQueue'lar asla kullanılmamalıdır (ve planlamayı yapan başka bir şeye hile yapamaz ve delege edemezsiniz, yani akka, çünkü bu da bir üretici / tüketici olduğu için engelleme / beklemeyi yapacaktır).
Adam Gent

33

LinkedBlockingQueuekuyruk boş veya dolu olduğunda tüketiciyi veya üreticiyi bloke eder ve ilgili tüketici / üretici iş parçacığı uykuya alınır. Ancak bu engelleme özelliğinin bir bedeli vardır: Her alım veya satım işlemi, üreticiler veya tüketiciler (eğer çoksa) arasında kilitlenir, bu nedenle birçok üretici / tüketicinin olduğu senaryolarda işlem daha yavaş olabilir.

ConcurrentLinkedQueuekilit kullanmıyor, ancak CAS , koyma / alma işlemlerinde birçok üretici ve tüketici iş parçacığı ile potansiyel olarak çekişmeyi azaltıyor. Ancak "beklemesiz" bir veri yapısı olmak, ConcurrentLinkedQueueboşken bloke olmaz, yani tüketicinin, örneğin tüketici iş parçacığının CPU'yu tüketmesi gibi "meşgul bekleme" yoluyla take()dönen nulldeğerlerle uğraşması gerekeceği anlamına gelir .

Yani hangisinin "daha iyi" olduğu tüketici iplerinin sayısına, tükettikleri / ürettikleri orana vb. Bağlıdır. Her senaryo için bir kıyaslama gereklidir.

ConcurrentLinkedQueueAçıkça daha iyi olan belirli bir kullanım durumu , üreticilerin işi sıraya koyarak ilk önce bir şeyler üretip işlerini bitirmeleridir ve yalnızca tüketiciler, kuyruk boşken yapılacaklarını bilerek tüketmeye başladıktan sonra . (burada üretici-tüketici arasında bir eşzamanlılık yoktur, sadece üretici-üretici ve tüketici-tüketici arasında)


burada bir şüphe var. Bahsettiğiniz gibi tüketici kuyruk boş olduğunda bekler ... ne kadar bekler. Kim beklememesini bildirecek?
Brinal

@brindal Bildiğim kadarıyla beklemesinin tek yolu bir döngü içinde. Buradaki cevaplarda pek dikkate alınmayan önemli bir sorundur. Sadece veri beklemek için bir döngü çalıştırmak çok fazla işlemci süresi gerektirir. Hayranlarınız canlanmaya başladığında bunu anlayacaksınız. Tek çare, döngüye bir uyku koymaktır. Dolayısıyla, tutarsız veri akışına sahip bir sistemdeki bir problemdir. Belki de AlexandruNedelcu'nun cevabını yanlış anlıyorum, ancak bir işletim sisteminin kendisi eşzamanlı bir sistemdir ve engellemeyen olay döngüleri ile dolu olsaydı, oldukça verimsiz olurdu.
orodbhen

tamam, ama unbounded blockingqueuekullanılırsa, CAS tabanlı eşzamanlı olarak kullanılmasından daha iyi olur muConcurrentLinkedQueue
amarnath harish

@orodbhen Uykuya dalmak israfı da ortadan kaldırmaz. İşletim sistemi uykudan çıkmak ve onu programlamak ve çalıştırmak için çok iş yapmak zorundadır. Mesajlar henüz mevcut değilse, işletim sisteminiz tarafından yapılan bu çalışma boşa gider. Özellikle üretici-tüketici problemi için tasarlandığı için BlockingQueue kullanmanın daha iyi olduğunu tavsiye ederim.
Nishit

aslında "tüket / üret oranı" kısmıyla çok ilgileniyorum, öyleyse oran yükselirse hangisi daha iyi?
ömrü

0

Başka bir çözüm (iyi ölçeklenmeyen) randevulu kanallardır: java.util.concurrent SynchronousQueue


0

Sıranız genişletilemezse ve yalnızca bir üretici / tüketici iş parçacığı içeriyorsa. Kilitsiz kuyruğu kullanabilirsiniz (Veri erişimini kilitlemenize gerek yoktur).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.