Node.js'de tek iş parçacıklı engellemeyen G / Ç modeli nasıl çalışır?


325

Ben bir Düğüm programcısı değilim, ama tek iş parçacıklı engellemeyen IO modelinin nasıl çalıştığıyla ilgileniyorum . Node-js-event-loop'u anlayan makaleyi okuduktan sonra gerçekten kafam karıştı. Model için bir örnek verdi:

c.query(
   'SELECT SLEEP(20);',
   function (err, results, fields) {
     if (err) {
       throw err;
     }
     res.writeHead(200, {'Content-Type': 'text/html'});
     res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');
     c.end();
    }
);

Que: Yalnızca tek bir iş parçacığı olduğu için iki istek A (önce gelir) ve B olduğunda, sunucu tarafı program A isteğini işleyecektir: Öncelikle: SQL sorgulama yapmak G / Ç beklemesi için bekleyen uyku ifadesidir. Ve program I/Obeklemede takılı kalıyor ve web sayfasını geride bırakan kodu yürütemiyor. Program bekleme sırasında B isteğine geçecek mi? Kanımca, tek iş parçacığı modeli nedeniyle, bir isteği diğerinden değiştirmenin bir yolu yoktur. Ancak örnek kodun başlığı, kodunuz dışında her şeyin paralel çalıştığını söylüyor .

(PS Düğümü hiç kullanmadığımdan beri kodu yanlış anladığımdan emin değilim.) Bekleme sırasında Düğüm A'yı nasıl B'ye çeviririm? Düğümün tek dişli tıkanmasız IO modelini basit bir şekilde açıklayabilir misiniz ? Bana yardım edebilirsen sevinirim. :)

Yanıtlar:


374

Node.js, desteklenen işletim sistemleri (en azından Unix, OS X ve Windows) tarafından sağlanan eşzamansız (engellemeyen) giriş / çıkış için apis / syscall'ları özetleyen çapraz platform kütüphanesi libuv üzerine kurulmuştur .

Asenkron IO

Bu programlama modelinde, dosya sistemi tarafından yönetilen cihazlarda ve kaynaklarda (soketler, dosya sistemi vb.) Açma / okuma / yazma işlemi çağıran iş parçacığını engellemez (tipik eşzamanlı c benzeri modelde olduğu gibi) ve yeni veriler veya olaylar mevcut olduğunda bildirilecek işlem (çekirdek / OS düzeyinde veri yapısında). Web sunucusu benzeri bir uygulama söz konusu olduğunda süreç, bildirilen olayın hangi istek / bağlama ait olduğunu bulmaktan ve isteği oradan işlemeye devam etmekten sorumludur. Bunun, yeni olayları işlemek için tek bir iş parçacıklı işlemin yürütülmesi için bir işlem dağıtıcısına vermesi gerektiğinden, işletim sisteminin isteğini başlatandan farklı bir yığın karesinde olacağınız anlamına geleceğini unutmayın.

Tarif ettiğim modelle ilgili problem, doğası gereği ardışık olmadığı için programcı için akıl alması ve akıl yürütmesinin zor olmamasıdır. "A fonksiyonunda talepte bulunmanız ve sonucu A'dan gelen yerlilerin genellikle mevcut olmadığı farklı bir fonksiyonda ele almanız gerekir."

Düğümün modeli (Devam Tarzı ve Etkinlik Döngüsü)

Düğüm, programcıyı belirli bir programlama stili kullanmaya teşvik ederek bu modeli biraz daha senkronize hale getirmek için javascript'in dil özelliklerinden yararlanma sorununu ele alır. ES talep eden her fonksiyonun imzası vardır function (... parameters ..., callback)ve talep edilen işlem tamamlandığında çağrılacak bir geri çağrı verilmelidir (çoğu zaman işletim sisteminin tamamlanma sinyalini beklemek için harcandığını unutmayın. başka işler yapmak için harcanan). Javascript'in kapanma desteği, geri arama gövdesi içindeki dış (çağrı) işlevinde tanımladığınız değişkenleri kullanmanıza olanak tanır - bu, düğüm çalışma zamanı tarafından bağımsız olarak çağrılacak farklı işlevler arasındaki durumu korumanıza olanak tanır. Ayrıca bkz . Devamlı Geçiş Stili .

Ayrıca, bir IO işlemi üreten bir işlev çağrıldıktan sonra, çağrı işlevi genellikle returndüğümün olay döngüsünü kontrol eder . Bu döngü, yürütülmesi planlanan bir sonraki geri aramayı veya işlevi çağırır (büyük olasılıkla ilgili olay işletim sistemi tarafından bildirildiği için) - bu, birden fazla isteğin eşzamanlı olarak işlenmesine izin verir.

Düğümün olay döngüsünü çekirdeğin dağıtım programına benzer şekilde düşünebilirsiniz : çekirdek, bekleyen IO tamamlandıktan sonra engellenen bir iş parçacığının yürütülmesini zamanlarken düğüm karşılık gelen olay gerçekleştiğinde bir geri arama zamanlayacaktır.

Yüksek eşzamanlılık, paralellik yok

Son bir açıklama olarak, "kodunuz hariç her şey paralel olarak çalışır" ifadesi, düğümünüzün kodunuzun yüz binlerce açık soketten gelen istekleri tek bir iş parçacığıyla çoğaltarak ve sıralayarak eşzamanlı olarak işlemesine izin verdiği noktayı yakalamak için iyi bir iş çıkarır. tek bir yürütme akışında mantık ("her şey paralel çalışır" demek muhtemelen doğru değildir - bkz. Eşzamanlılık - Paralellik - Fark nedir? ). Çoğu zaman ağ veya disk (veritabanı / soketler) beklemeye harcandığından ve mantık gerçekten CPU yoğun olmadığından, yani: IO'ya bağlı iş yükleri için iyi çalıştığından, bu webapp sunucuları için oldukça iyi çalışır .


45
Takip eden sorular: G / Ç aslında nasıl olur? Düğüm sisteme bir istekte bulunuyor ve bittiğinde bildirimde bulunulmasını istiyor. Sistem G / Ç yapan bir iş parçacığı çalıştırıyor mu, yoksa sistem G / Ç'yi kesintiler kullanarak donanım düzeyinde eşzamansız olarak mı yapıyor? Bir yerde bir şey I / O'nun bitmesini beklemek zorunda ve bu bitene kadar engellenecek ve bir miktar kaynak tüketecek.
Philip

6
Bu takip yorumunun aşağıdaki @ user568109 tarafından yanıtlandığını fark ettim, keşke bu iki yanıtı birleştirmenin bir yolu olsaydı.
lfalin

4
Keşke iki kat daha uzun bir yanıt yazabilseydiniz, bu yüzden iki kat daha iyi anlardım.
Rafael Eyng

Düğüm, kayıt için birçok yerde desteklenir. MIPS32 yönlendiricileri için ürün yazılımı tasarlarken, Node.JS, OpenWRT aracılığıyla bunlarda çalıştırılabilir.
Qix - MONICA

Apache üzerinden nasıl puan alıyor? Apache aynı zamanda ayrı bir iş parçacığıyla eşzamanlı bağlantıları da yönetebilir.
Suhail Gupta

210

Biraz perspektif vermek için node.js'yi apache ile karşılaştırmama izin verin.

Apache çok iş parçacıklı bir HTTP sunucusudur, sunucunun aldığı her istek için bu isteği işleyen ayrı bir iş parçacığı oluşturur.

Öte yandan Node.js olay güdümlüdür ve tüm istekleri tek bir iş parçacığından zaman uyumsuz olarak işler.

A ve B apache'de alındığında, istekleri işleyen iki iş parçacığı oluşturulur. Her biri ayrı ayrı sorguyu işler, her biri sayfayı sunmadan önce sorgu sonuçlarını bekler. Sayfa yalnızca sorgu tamamlanana kadar sunulur. Sunucu, sonucu alana kadar iş parçacığının geri kalanını çalıştıramadığı için sorgu getirme engelleniyor.

Düğümde, c.query eşzamansız olarak işlenir, yani c.query A için sonuçları alırken, C için c.query'yi işlemek için atlar ve sonuçlar A'ya ulaştığında sonuçları geri gönderen geri gönderir. tepki. Node.js getirme bittiğinde geri arama yapmayı bilir.

Kanımca, tek bir iş parçacığı modeli olduğu için, bir istekten diğerine geçmenin bir yolu yoktur.

Aslında düğüm sunucusu her zaman sizin için bunu yapar. Anahtar yapmak için, (eşzamansız davranış) kullanacağınız çoğu işlevin geri çağrıları olacaktır.

Düzenle

SQL sorgusu mysql kütüphanesinden alınır . SQL isteklerini sıralamak için geri çağırma stilini ve olay yayıcıyı uygular. Bunları eşzamansız olarak yürütmez, bu da engellemeyen G / Ç soyutlamasını sağlayan dahili libuv dişleri tarafından yapılır . Bir sorgu yapmak için aşağıdaki adımlar gerçekleşir:

  1. Db'ye bir bağlantı açın, bağlantının kendisi eşzamansız olarak yapılabilir.
  2. Db bağlandıktan sonra sorgu sunucuya iletilir. Sorgular kuyruğa alınabilir.
  3. Ana olay döngüsü, geri arama veya olayla tamamlandığını bildirir.
  4. Ana döngü geri arama / olay işleyicinizi yürütür.

Http sunucusuna gelen istekler de benzer şekilde işlenir. İç iş parçacığı mimarisi şuna benzer:

node.js olay döngüsü

C ++ iş parçacıkları, zaman uyumsuz G / Ç'yi (disk veya ağ) yapan libuvlardır. Ana olay döngüsü, iş parçacığı havuzuna istek gönderildikten sonra yürütülmeye devam eder. Beklemediği veya uyumadığı için daha fazla istek kabul edebilir. SQL sorguları / HTTP istekleri / dosya sistemi okumaları bu şekilde gerçekleşir.


16
Diyagram çok faydalıdır.
Anmol Saraf

14
Bekleyin, diyagramınızda "dahili C ++ threadpool" var, bu da tüm IO engelleme işlemlerinin bir iş parçacığı oluşturacağı anlamına geliyor, değil mi? Düğüm uygulamam bazı istekler her istek için çalışıyorsa , Düğüm modeli ile Apache modeli arasında neredeyse hiç fark yok mu? Bu kısmı üzülmüyorum.
gav.newalkar

21
@ gav.newalkar Bir iş parçacığı oluşturmazlar, istekler sıraya alınır. Threadpool'daki iplikler bunları işler. İplikler dinamik değildir ve Apache'deki gibi isteğe bağlıdır. Genellikle sabittirler ve sistemden sisteme farklılık gösterirler.
user568109

10
@ user568109 Ancak Apache de bir threadpool kullanıyor ( httpd.apache.org/docs/2.4/mod/worker.html ). Nihayetinde node.js ile yapılan bir kurulum arasındaki fark, Apache'nin önünde sadece threadpool'ın bulunduğu yerde olandan farklıdır, değil mi?
Kris

13
Bu diyagram resmi belgelerin ilk sayfasında olmalıdır.
bouvierr

52

Node.js perde arkasında libuv kullanır . libuv bir iş parçacığı havuzuna sahiptir (varsayılan olarak 4 boyuttadır). Bu nedenle Node.js , eşzamanlılık elde etmek için iş parçacıklarını kullanır.

Ancak , kodunuz tek bir iş parçacığında çalışır (yani, Node.js işlevlerinin tüm geri çağrıları, aynı iş parçacığında, yani döngü iş parçacığı veya olay döngüsü olarak adlandırılır). İnsanlar "Node.js tek bir iş parçacığında çalışır" derken gerçekten "Node.js'nin geri çağrıları tek bir iş parçacığında çalışır" diyorlar.


1
Kısa ama Net cevap (y)
Sudhanshu Gaur

1
iyi cevap I / O bu ana olay döngü, döngü iplik, istek iş parçacığı dışında olduğunu
eklerdim

2 saat aradığım cevap, tek iş parçacıklı uygulamada eşzamanlılığın nasıl yönetildiği
Muhammad Ramzan

evet, 'bir sonraki seviye' cevabını almak zor. Bu, ES'nin gerçekte nerede yapıldığını açıklıyor (başka bir yerde bir iplik havuzunda)
Oliver Shaw

9

Node.js, olay döngüsü programlama modelini temel alır. Olay döngüsü tek bir iş parçacığında çalışır ve tekrar tekrar olayları bekler ve daha sonra bu olaylara abone olan olay işleyicilerini çalıştırır. Olaylar örneğin olabilir

  • zamanlayıcı bekleme tamamlandı
  • sonraki veri yığını bu dosyaya yazılmaya hazır
  • yeni bir HTTP isteği geliyor

Tüm bunlar tek bir iş parçacığında çalışır ve paralel olarak hiçbir JavaScript kodu çalıştırılmaz. Bu olay işleyicileri küçük olduğu ve daha fazla etkinlik bekledikleri sürece her şey iyi çalışır. Bu, tek bir Node.js işlemi tarafından birden çok isteğin aynı anda işlenmesine olanak tanır.

(Olayların kaynaklandığı yerde kaputun altında biraz sihir var. Bazıları paralel olarak çalışan düşük seviyeli işçi ipliklerini içeriyor.)

Bu SQL durumunda, veritabanı sorgusu yapmak ve sonuçlarını geri aramaya almak arasında bir çok şey (olay) vardır . Bu süre zarfında olay döngüsü, uygulamanın içine hayat pompalamaya ve diğer istekleri her seferinde küçük bir olay ilerletmeye devam eder. Bu nedenle, aynı anda birden fazla istek sunuluyor.

olay döngüsü üst düzey görünümü

: Göre "- node.js arkasında çekirdek kavram 10,000ft gelen olay döngü" .


5

C.query () işlevinin iki bağımsız değişkeni vardır

c.query("Fetch Data", "Post-Processing of Data")

Bu durumda "Verileri Getir" işlemi bir DB-Query'dir, şimdi bu bir işçi iş parçacığını oluşturarak ve ona DB-Query gerçekleştirme görevi vererek Node.js tarafından işlenebilir. (Node.js'nin dahili olarak iş parçacığı oluşturabileceğini unutmayın). Bu, fonksiyonun herhangi bir gecikme olmadan anında geri dönmesini sağlar

İkinci argüman "Verilerin Post-Processing" bir geri çağrı fonksiyonudur, düğüm çerçevesi bu geri çağrıyı kaydeder ve olay döngüsü tarafından çağrılır.

Böylece ifade c.query (paramenter1, parameter2)anında geri dönerek düğümün başka bir talebi karşılamasını sağlar.

Not: Düğümü anlamaya başladım, aslında bunu @Philip'e yorum olarak yazmak istedim, ancak yeterince itibar puanı olmadığı için bu yüzden bir cevap olarak yazdım.


3

biraz daha okursanız - "Tabii ki, arka uçta, DB erişimi ve işlem yürütme için iş parçacıkları ve süreçler vardır. Ancak, bunlar açıkça kodunuza maruz kalmaz, bu yüzden bilmeden başka onlar hakkında endişelenemezsiniz örneğin veritabanıyla veya diğer işlemlerle G / Ç etkileşimlerinin, her iş parçacığının perspektifinden eşzamansız olacağı için, bu iş parçacıklarından elde edilen sonuçlar olay döngüsü aracılığıyla kodunuza döndürülür. "

about - "kodunuz hariç her şey paralel çalışır" - kodunuz senkronize olarak yürütülür, G / Ç'yi beklemek gibi eşzamansız bir işlem başlattığınızda, olay döngüsü her şeyi işler ve geri çağrıyı başlatır. sadece düşünmeniz gereken bir şey değil.

örneğinizde: A (önce gelir) ve B olmak üzere iki istek vardır. A isteğini yürütürseniz, kodunuz senkronize olarak çalışmaya devam eder ve B isteğini yürütmeye devam eder. olay döngüsü, A isteğini işler, tamamlandığında A isteğini sonuç, aynı B talebine gider.


3
"Tabii ki, arka uçta, DB erişim ve süreç yürütme için iş parçacıkları ve süreçleri vardır. Ancak, bunlar açıkça kodunuza maruz kalmaz" - Bu ifadeden alırsam, o zaman ne Düğüm arasında herhangi bir fark görmüyorum yapmak veya herhangi bir çok iş parçacıklı çerçeve - diyelim ki Java'nın Spring Framework - yapar. İş parçacıkları var, ancak yaratımlarını denetlemiyorsunuz.
Rafael Eyng

@RafaelEyng Birden fazla istek serisini işlemek için düşünüyorum, düğüm her zaman bunun için tek bir iş parçacığına sahip olacak. Her geri aramanın db erişimi gibi diğer işlemlerin yanı sıra iş parçacıklarının yeni örneğine konulup konulmadığından emin değilim ama en azından, işlemden önce sırada beklemek zorunda kalacak bir istek aldığında düğümün iş parçacıklarını başlattığını kesinlikle bilmiyoruz. geri arama).
Soğuk Cerberus

1

Tamam, şu ana kadar çoğu şey açık olmalı ... zor kısım SQL : eğer gerçekte başka bir iş parçacığında veya süreçte tam olarak çalışmıyorsa , SQL yürütmesi ayrı adımlara bölünmelidir ( SQL işlemci engellenmeyen olanlar infaz edildiği,)! uyumsuz olarak yürütülmesi için yapılan ve engelleme olanlar (örneğin uyku) aslında olabilir bir alarm kesme / olay olarak (çekirdeğe aktarılan) ve için olay listesine koymak Ana döngü.

Yani, örneğin SQL'in yorumu derhal yapılır, ancak bekleme sırasında (bazı kqueue, epoll, ... yapısında çekirdek tarafından gelecekteki bir olay olarak saklanır; diğer IO işlemleri ile birlikte) ) ana döngü başka şeyler yapabilir ve sonunda bu ES'lerden bir şey olup olmadığını kontrol eder ve bekler.

Bu nedenle, tekrar ifade etmek için: program asla takılmaya (izin verilmez), uyku çağrıları asla yürütülmez. Görevleri çekirdek (bir şeyler yazın, bir şeyin ağ üzerinden gelmesini bekleyin, zamanın geçmesini bekleyin) veya başka bir iş parçacığı veya işlem tarafından yapılır. - Düğüm işlemi, bu olaylardan en az birinin, her olay döngüsü döngüsünde işletim sistemine yapılan tek engelleme çağrısında çekirdek tarafından tamamlanıp tamamlanmadığını denetler. Engelleme olmayan her şey yapıldığında bu noktaya ulaşılır.

Açık? :-)

Düğümü bilmiyorum. Peki c.query nereden geliyor?


kqueue epoll, linux çekirdeğinde ölçeklenebilir asenkron I / O bildirimi içindir. Düğüm bunun için libuv'a sahiptir. Düğüm tamamen kullanıcı arazisinde. Çekirdeğin ne uyguladığına bağlı değildir.
user568109 16:13

1
@ user568109, libuv Node'nin orta adamıdır. Herhangi bir eşzamansız çerçeve (doğrudan ya da değil) çekirdekteki bazı eşzamansız G / Ç desteğine bağlıdır. Yani?
Robert Siemer

Karışıklık için özür dilerim. Soket işlemleri çekirdekten engellemeyen G / Ç gerektirir. Asenkron yol tutuşuyla ilgilenir. Ancak eşzamansız dosya G / Ç libuv tarafından yönetilir. Cevabınız bunu söylemiyor. Çekirdek tarafından ele alındığında her ikisine de aynı şekilde davranır.
user568109
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.