İş parçacığı havuzu ne zaman kullanılır?


104

Bu yüzden Node.js'nin nasıl çalıştığını anlıyorum: Bir olayı alan ve ardından onu bir çalışan havuzuna devreten tek bir dinleyici iş parçacığı var. İş parçacığı, işi tamamladığında dinleyiciye bildirimde bulunur ve dinleyici daha sonra yanıtı arayana geri gönderir.

Sorum şu: Node.js'de bir HTTP sunucusu kurarsam ve yönlendirilmiş yol olaylarımdan birinde ("/ test / sleep" gibi) uyku işlevini çağırırsam, tüm sistem durur. Tek dinleyici dizisi bile. Ama benim anladığım kadarıyla bu kod işçi havuzunda oluyor.

Şimdi, tam tersine, Mongoose'u MongoDB ile konuşmak için kullandığımda, DB okumaları pahalı bir G / Ç işlemi. Düğüm, işi bir iş parçacığına devredebilir ve tamamlandığında geri aramayı alabilir gibi görünüyor; DB'den yükleme için geçen süre sistemi engellemiyor gibi görünüyor.

Node.js, dinleyici iş parçacığı yerine iş parçacığı havuzu iş parçacığı kullanmaya nasıl karar verir? Neden uyuyan ve yalnızca bir iş parçacığı havuzu iş parçacığını engelleyen olay kodu yazamıyorum?


@Tobi - Bunu gördüm. Hala soruma cevap vermiyor. Çalışma başka bir iş parçacığında olsaydı, uyku dinleyiciyi değil sadece o diziyi etkilerdi.
Haney

8
Gerçek bir soru, bir şeyi kendi kendinize anlamaya çalıştığınız ve labirente çıkış bulamadığınız zaman, yardım istediniz.
Rafael Eyng

Yanıtlar:


242

Düğümün nasıl çalıştığına dair anlayışınız doğru değil ... ancak bu yaygın bir yanılgıdır, çünkü durumun gerçekliği aslında oldukça karmaşıktır ve tipik olarak "düğüm tek iş parçacıklıdır" gibi şeyleri aşırı basitleştiren özlü küçük ifadelere indirgenir. .

Şimdilik, küme ve web çalışanı iş parçacıkları aracılığıyla açık çoklu işlemeyi / çoklu iş parçacığını görmezden geleceğiz ve tipik iş parçacıklı olmayan düğüm hakkında konuşacağız.

Düğüm, tek bir olay döngüsünde çalışır. Tek iş parçacıklı ve sadece bir ileti dizisi elde edersiniz. Yazdığınız tüm javascript bu döngüde çalışır ve eğer bu kodda bir engelleme işlemi olursa, tüm döngüyü bloke eder ve bitene kadar başka hiçbir şey olmaz. Bu, hakkında çok şey duyduğunuz düğümün tipik olarak tek iş parçacıklı doğasıdır. Ancak, resmin tamamı değil.

Genellikle C / C ++ ile yazılan bazı işlevler ve modüller, eşzamansız G / Ç'yi destekler. Bu işlevleri ve yöntemleri çağırdığınızda, çağrıyı bir çalışan iş parçacığına aktarmayı dahili olarak yönetirler. Örneğin, fsmodülü bir dosya talep etmek için kullandığınızda, modül fsbu çağrıyı bir çalışan iş parçacığına iletir ve bu çalışan yanıtını bekler, daha sonra bu çağrı olmadan çalkalanan olay döngüsüne geri döner. bu arada. Tüm bunlar sizden, düğüm geliştiricisinden soyutlanmıştır ve bir kısmı libuv kullanımıyla modül geliştiricilerinden soyutlanmıştır. .

Denis Dollfus'un yorumlarda ( bu yanıttan benzer bir soruya) işaret ettiği gibi, libuv tarafından eşzamansız G / Ç elde etmek için kullanılan strateji her zaman bir iş parçacığı havuzu değildir, özellikle httpmodül durumunda farklı bir strateji şu anda kullanıldı. Buradaki amaçlarımız için, asenkronize bağlamın nasıl elde edildiğini (libuv kullanarak) ve libuv tarafından sağlanan iş parçacığı havuzunun, asenkronize ulaşmak için bu kitaplık tarafından sunulan çoklu stratejilerden biri olduğunu not etmek önemlidir.


Çoğunlukla ilgili bir teğette, bu mükemmel makalede düğümün eşzamansızlığı nasıl sağladığına ve bazı ilgili potansiyel sorunlara ve bunlarla nasıl başa çıkılacağına dair çok daha derin bir analiz var . Birçoğu yukarıda yazdıklarımı genişletiyor, ancak ek olarak şunu da belirtiyor:

  • Projenize dahil ettiğiniz ve yerel C ++ ve libuv kullanan herhangi bir harici modül muhtemelen iş parçacığı havuzunu kullanacaktır (düşünün: veritabanı erişimi)
  • libuv'un varsayılan iş parçacığı havuzu boyutu 4'tür ve iş parçacığı havuzuna erişimi yönetmek için bir kuyruk kullanır - sonuç şu ki, hepsi aynı anda giden 5 uzun süreli DB sorgunuz varsa, bunlardan biri (ve diğer eşzamansız iş parçacığı havuzuna dayanan eylem), daha başlamadan önce bu sorguların bitmesini bekliyor olacak
  • İş UV_THREADPOOL_SIZEparçacığı havuzu gerekmeden ve oluşturulmadan önce yaptığınız sürece , ortam değişkeni aracılığıyla iş parçacığı havuzunun boyutunu artırarak bunu hafifletebilirsiniz :process.env.UV_THREADPOOL_SIZE = 10;

Düğümde geleneksel çoklu işlemeyi veya çoklu iş parçacığını istiyorsanız, bunu yerleşik clustermodülden veya yukarıda belirtilenler gibi çeşitli diğer modüllerden elde webworker-threadsedebilir veya işinizi bir şekilde parçalayarak ve manuel olarak kullanarak setTimeoutveya setImmediateveya process.nextTickdiğer işlemlerin tamamlanmasına izin vermek için çalışmanızı duraklatıp daha sonraki bir döngüde devam ettirmek için (ancak bu önerilmez).

Lütfen, javascript'te uzun süreli / engelleyici kod yazıyorsanız, muhtemelen bir hata yaptığınızı unutmayın. Diğer diller çok daha verimli çalışacak.


1
Vay canına, bu benim için her şeyi tamamen açıklıyor. Çok teşekkür ederim @Jason!
Haney

5
Sorun değil :) Kendimi çok uzun zaman önce olmadığın bir yerde buldum ve iyi tanımlanmış bir cevaba gelmek zordu çünkü bir tarafta cevabı açık olan C / C ++ geliştiricilerine sahipsin, diğer tarafta ise tipik Daha önce bu tür sorulara fazla derinlemesine dalmamış web geliştiricileri. C seviyesine indiğinizde cevabımın teknik olarak% 100 doğru olduğundan bile emin değilim, ama geniş vuruşlarda doğru.
Jason

3
Ağ istekleri için iş parçacığı havuzunu kullanmak büyük bir kaynak israfı olur. Bu soruya göre "Eşzamansız ağ G / Ç'yi epoll, kqueue ve IOCP gibi farklı platformlardaki eşzamansız G / Ç arabirimlerine dayalı, iş parçacığı havuzu olmadan yapar" - bu mantıklıdır.
Denis Dollfus

1
... bununla birlikte, ana javascript iş parçacığında doğrudan bazı ağır işler yaparsanız veya yeterli kaynağa sahip değilseniz veya bunları iş parçacığına yeterli boşluk sağlamak için uygun şekilde yönetmezseniz, daha düşük bir eşzamanlılıkta gecikme yaşayabilirsiniz. eşik - sonuç şu ki, aynı sistem kaynakları için, tipik olarak node.js ile diğer seçeneklerden daha yüksek thruput deneyimi yaşarsınız (ancak diğer dillerde buna meydan okumayı amaçlayan başka olay tabanlı sistemler de vardır - ben yine de son karşılaştırmalar görüldü) - olay tabanlı bir modelin iş parçacıklı bir modelden daha iyi performans gösterdiği açıktır.
Jason

1
@Aabid Dinleyici iş parçacığı bir veritabanı sorgusu yürütmez, bu nedenle bu 10 sorgunun tamamının tamamlanması yaklaşık 6 saniye sürer (varsayılan iş parçacığı havuzu boyutu 4'tür). Javascript'te o veritabanı sorgusunun sonuçlarının tamamlanmasını gerektirmeyen herhangi bir iş yapmanız gerekirse, örneğin iş parçacığı havuzu tarafından tamamlanacak eşzamansız çalışma gerektirmeyen daha fazla istek gelirse, ana bilgisayarda çalışmaya devam edecektir. olay döngüsü.
Jason

20

Bu yüzden Node.js'nin nasıl çalıştığını anlıyorum: Bir olayı alan ve ardından onu bir çalışan havuzuna devreten tek bir dinleyici iş parçacığı var. İş parçacığı, işi tamamladığında dinleyiciye bildirimde bulunur ve dinleyici daha sonra yanıtı arayana geri gönderir.

Bu gerçekten doğru değil. Node.js, javascript yürütme yapan yalnızca tek bir "çalışan" iş parçacığına sahiptir. Düğüm içinde GÇ işlemeyi gerçekleştiren iş parçacıkları vardır, ancak bunları "çalışan" olarak düşünmek bir yanlış anlamadır. Gerçekten sadece IO işleme ve düğümün dahili uygulamasının birkaç başka detayı vardır, ancak bir programcı olarak MAX_LISTENERS gibi birkaç çeşitli parametreden başka davranışlarını etkileyemezsiniz.

Sorum şu: Node.js'de bir HTTP sunucusu kurarsam ve yönlendirilmiş yol olaylarımdan birinde ("/ test / sleep" gibi) uyku işlevini çağırırsam, tüm sistem durur. Tek dinleyici dizisi bile. Ama benim anladığım kadarıyla bu kod işçi havuzunda oluyor.

JavaScript'te uyku mekanizması yoktur. "Uyku" nun ne anlama geldiğini düşündüğünüz bir kod parçacığı yayınlarsanız, bunu daha somut tartışabiliriz. time.sleep(30)Örneğin python'daki gibi bir şeyi simüle etmek için çağırmak için böyle bir işlev yok . Var setTimeoutama bu temelde uyku DEĞİLDİR. setTimeoutve diğer kod bitlerinin ana yürütme iş parçacığında yürütülebilmesi için olay döngüsünü bloke etmek yerine setIntervalaçıkça serbest bırakın . Yapabileceğiniz tek şey, CPU'yu bellek içi hesaplama ile meşgul etmektir; bu, gerçekten de ana yürütme iş parçacığını aç bırakacak ve programınızı yanıt vermez hale getirecektir.

Node.js, dinleyici iş parçacığı yerine iş parçacığı havuzu iş parçacığı kullanmaya nasıl karar verir? Neden uyuyan ve yalnızca bir iş parçacığı havuzu iş parçacığını engelleyen olay kodu yazamıyorum?

Ağ GÇ'si her zaman eşzamansızdır. Hikayenin sonu. Disk IO'nun hem eşzamanlı hem de eşzamansız API'leri vardır, bu nedenle "karar" yoktur. node.js, normal eşzamansız yerine eşitleme olarak adlandırdığınız API temel işlevlerine göre davranır. Örneğin: fs.readFilevs fs.readFileSync. Alt süreçler için ayrıca ayrı child_process.execvechild_process.execSync API'ler de vardır.

Temel kural, her zaman eşzamansız API'leri kullanmaktır. Senkronizasyon API'lerini kullanmanın geçerli nedenleri, bağlantıları dinlemeden önce bir ağ hizmetindeki başlatma kodu veya oluşturma araçları ve benzeri şeyler için ağ isteklerini kabul etmeyen basit komut dosyaları içindir.


1
Bu eşzamansız API'ler nereden geliyor? Ne dediğinizi anlıyorum, ancak bu API'leri yazan kişi IOCP / async'i seçti. Bunu nasıl yapmayı seçtiler?
Haney

3
Sorusu, kendi zaman yoğun kodunu nasıl engelleyeceği değil nasıl yazacağıdır.
Jason

1
Evet. Düğüm, temel UDP, TCP ve HTTP ağları sağlar. YALNIZCA asenkron "havuz tabanlı" API'ler sağlar. Dünyadaki istisnasız tüm node.js kodu, mevcut olan her şey olduğu için bu havuz tabanlı eşzamansız API'leri kullanır. Dosya sistemi ve alt süreçler farklı bir hikayedir, ancak ağ sürekli olarak eşzamansızdır.
Peter Lyons

4
Dikkat et Peter, onun çaydanlık için meşhur çanak sen ol. Ağ API'sini kullananların bunu nasıl yaptığını değil, ağ API yazarlarının bunu nasıl yaptığını bilmek istiyor. Sonunda düğümün nasıl davrandığına dair bir anlayış kazandım: bloke edici olmayan olaylar, çünkü ağ veya diğer yerleşik eşzamansız API'larla hiçbir ilgisi olmayan kendi engellemeyen kodumu yazmak istedim. David'in de aynısını yapmak istediği oldukça açık.
Jason

2
Düğüm, GÇ için iş parçacığı havuzlarını kullanmaz, yerel engellemesiz GÇ kullanır, tek istisna, fsbildiğim kadarıyla
vkurchatkin

2

İş parçacığı havuzu nasıl, ne zaman ve kim kullandı:

Öncelikle bir bilgisayarda Node kullandığımızda / kurduğumuzda, bilgisayarda node işlemi olarak adlandırılan diğer işlemler arasında bir işlem başlatır ve siz onu öldürene kadar çalışmaya devam eder. Ve bu çalışan süreç bizim sözde tek iş parçacığımızdır.

görüntü açıklamasını buraya girin

Dolayısıyla, tek iş parçacığı mekanizması bir düğüm uygulamasını engellemeyi kolaylaştırır, ancak bu, Node.js'nin tabloya getirdiği benzersiz özelliklerden biridir. Dolayısıyla, düğüm uygulamanızı çalıştırırsanız, yalnızca tek bir iş parçacığında çalışacaktır. Uygulamanıza aynı anda erişen 1 veya milyon kullanıcınız olması fark etmez.

Öyleyse, düğüm uygulamanızı başlattığınızda, nodejs'nin tek iş parçacığında tam olarak ne olduğunu anlayalım. İlk önce program başlatılır, ardından tüm üst düzey kod çalıştırılır, bu da herhangi bir geri arama işlevinde bulunmayan tüm kodların ( tüm geri arama işlevlerindeki tüm kodların olay döngüsü altında yürütüleceğini unutmayın ) anlamına gelir.

Bundan sonra, yürütülen tüm modüller kodu daha sonra tüm geri aramayı kaydeder, son olarak, uygulamanız için olay döngüsü başlatılır.

görüntü açıklamasını buraya girin

Bu nedenle, daha önce tartıştığımız gibi, tüm geri çağırma işlevleri ve bu işlevlerin içindeki kodlar olay döngüsü altında çalıştırılacaktır. Olay döngüsünde, yükler farklı aşamalarda dağıtılır. Her neyse, burada olay döngüsü hakkında tartışmayacağım.

İş parçacığı havuzunun daha iyi anlaşılması için sizden olay döngüsünde, bir geri arama işlevinin içindeki kodların başka bir geri arama işlevi içindeki kodların yürütülmesini tamamladıktan sonra çalıştırılacağını hayal etmenizi rica ediyorum, şimdi bazı görevler gerçekten çok ağırsa. Daha sonra düğümlerimizin tek iş parçacığını bloke ederler. Ve böylece, olay döngüsü gibi iş parçacığı havuzunun geldiği yer burası, libuv kitaplığı tarafından Node.js'ye sağlanır.

Dolayısıyla iş parçacığı havuzu nodejs'nin bir parçası değildir, libuv tarafından ağır görevleri libuv'a devretmek için sağlanır ve libuv bu kodları kendi evrelerinde çalıştırır ve çalıştırıldıktan sonra libuv sonuçları olay döngüsündeki olaya döndürür.

görüntü açıklamasını buraya girin

İş parçacığı havuzu bize dört ek iş parçacığı verir, bunlar ana tek iş parçacığından tamamen ayrıdır. Ve aslında 128 iş parçacığına kadar yapılandırabiliriz.

Böylece tüm bu iplikler birlikte bir iş parçacığı havuzu oluşturdu. ve olay döngüsü daha sonra ağır görevleri iş parçacığı havuzuna otomatik olarak yükleyebilir.

İşin eğlenceli yanı, tüm bunların perde arkasında otomatik olarak gerçekleşmesidir. İş parçacığı havuzuna neyin girip neyin gitmeyeceğine karar veren biz geliştiriciler değiliz.

İş parçacığı havuzuna giden birçok görev var, örneğin

-> All operations dealing with files
->Everyting is related to cryptography, like caching passwords.
->All compression stuff
->DNS lookups

0

Bu yanlış anlama, yalnızca önleyici çoklu görev ile işbirliğine dayalı çoklu görev arasındaki farktır ...

Uyku, tüm karnavalı kapatır, çünkü tüm gezintilere gerçekten bir satır vardır ve siz kapıyı kapattınız. Bunu "bir JS yorumlayıcısı ve diğer bazı şeyler" olarak düşünün ve konuları göz ardı edin ... sizin için sadece bir iş parçacığı var, ...

... bu yüzden engellemeyin.

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.