Bu soruyu sormanız gerekiyorsa, muhtemelen çoğu web uygulamasının / hizmetinin ne yaptığına aşina değilsinizdir. Muhtemelen tüm yazılımların bunu yaptığını düşünüyorsunuz:
user do an action
│
v
application start processing action
└──> loop ...
└──> busy processing
end loop
└──> send result to user
Ancak, web uygulamaları veya gerçekten arka uç olarak veritabanına sahip herhangi bir uygulama bu şekilde çalışmaz. Web uygulamaları bunu yapar:
user do an action
│
v
application start processing action
└──> make database request
└──> do nothing until request completes
request complete
└──> send result to user
Bu senaryoda, yazılım çalışma zamanının çoğunu veritabanının geri dönmesini bekleyerek% 0 CPU zamanı kullanarak geçirir.
Çok iş parçacıklı ağ uygulaması:
Çok iş parçacıklı ağ uygulamaları yukarıdaki iş yükünü şu şekilde işler:
request ──> spawn thread
└──> wait for database request
└──> answer request
request ──> spawn thread
└──> wait for database request
└──> answer request
request ──> spawn thread
└──> wait for database request
└──> answer request
Böylece iş parçacığı zamanlarının çoğunu% 0 CPU kullanarak veritabanının veri döndürmesini bekler. Bunu yaparken, her bir iş parçacığı vb. İçin tamamen ayrı bir program yığını içeren bir iş parçacığı için gereken belleği ayırmak zorunda kalıyorlardı. Ayrıca, tam bir işlem başlatmak kadar pahalı olmayan bir iş parçacığını başlatmak zorunda kalacaklardır. ucuz.
Tekli iş parçacıklı olay döngüsü
Zamanımızın çoğunu% 0 CPU kullanarak harcadığımız için, neden CPU kullanmadığımızda bazı kodlar çalıştırmıyorsunuz? Bu şekilde, her istek yine de çok iş parçacıklı uygulamalarla aynı miktarda CPU zamanı alacaktır, ancak bir iş parçacığı başlatmamız gerekmez. Yani bunu yapıyoruz:
request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response
Uygulamada her iki yaklaşım da veriyi kabaca aynı gecikmeyle döndürür, çünkü işleme hakim olan veritabanı yanıt süresidir.
Buradaki ana avantaj, yeni bir iplik üretmemize gerek olmamasıdır, bu yüzden bizi yavaşlatacak çok fazla malloc yapmamız gerekmez.
Büyü, görünmez diş çekme
Görünüşte gizemli olan şey, yukarıdaki her iki yaklaşımın da iş yükünü "paralel" olarak çalıştırmayı nasıl başardığıdır? Cevap veritabanının iş parçacığı olmasıdır. Dolayısıyla, tek iş parçacıklı uygulamamız aslında başka bir işlemin çok iş parçacıklı davranışından yararlanıyor: veritabanı.
Tek kullanımlık yaklaşımın başarısız olduğu yerler
Verileri döndürmeden önce çok sayıda CPU hesaplaması yapmanız gerekiyorsa, tek iş parçacıklı bir uygulama büyük ölçüde başarısız olur. Şimdi, veritabanı sonucunu işlemek için bir döngü demek istemiyorum. Bu hala çoğunlukla O (n). Demek istediğim, Fourier dönüşümü (örneğin mp3 kodlaması), ışın izleme (3D render) vb.
Tek kullanımlık uygulamaların bir başka özelliği, yalnızca tek bir CPU çekirdeği kullanmasıdır. Yani dört çekirdekli bir sunucunuz varsa (günümüzde nadir değildir) diğer 3 çekirdeği kullanmıyorsunuzdur.
Çok iş parçacıklı yaklaşımın başarısız olduğu yerler
İş parçacığı başına çok fazla RAM ayırmanız gerekiyorsa, çok iş parçacıklı bir uygulama büyük ölçüde başarısız olur. Birincisi, RAM kullanımının kendisi, tek iş parçacıklı bir uygulama kadar çok istekte bulunamayacağınız anlamına gelir. Daha da kötüsü, malloc yavaştır. Çok sayıda nesne tahsis etmek (modern web çerçeveleri için ortaktır), potansiyel olarak tek başına işlenmiş uygulamalardan daha yavaş olabileceğimiz anlamına gelir. Bu, node.js'nin genellikle kazandığı yerdir.
Çok iş parçacığını daha da kötüleştiren bir kullanım örneği, iş parçacığınızda başka bir komut dosyası dili çalıştırmanız gerektiğidir. Öncelikle genellikle bu dil için tüm çalışma zamanını, sonra komut dosyanız tarafından kullanılan değişkenleri yanlış kullanmanız gerekir.
Bu nedenle, C veya go veya java'da ağ uygulamaları yazıyorsanız, iş parçacığı yükü genellikle çok kötü olmaz. PHP veya Ruby sunmak için bir C web sunucusu yazıyorsanız, javascript veya Ruby veya Python'da daha hızlı bir sunucu yazmak çok kolaydır.
Hibrit yaklaşım
Bazı web sunucuları karma bir yaklaşım kullanır. Örneğin Nginx ve Apache2, ağ işleme kodlarını olay döngülerinin iş parçacığı havuzu olarak uygular. Her iş parçacığı, aynı anda tek iş parçacıklı istekleri işleyen bir olay döngüsü çalıştırır, ancak istekler birden çok iş parçacığı arasında yük dengelidir.
Bazı tek iş parçacıklı mimariler de hibrit bir yaklaşım kullanır. Tek bir işlemden birden çok iş parçacığı başlatmak yerine birden çok uygulama başlatabilirsiniz - örneğin, dört çekirdekli bir makinede 4 node.js sunucusu. Sonra iş yükünü süreçler arasında yaymak için bir yük dengeleyici kullanırsınız.
Aslında iki yaklaşım teknik olarak birbirinin aynısıdır.