Yan yana
ThreadvsCoroutine
Thread (iş parçacığı) ile coroutine arasındaki fark nedir?
Güncellendi 3 dk okuma8 fark
Kısaca
Thread'leri işletim sistemi zamanlar ve her an kesebilir; coroutine'ler yalnızca seçtikleri yerde durur, bu yüzden binlercesi tek bir thread'i paylaşabilir.
Thread
İş Parçacığı
Thread, işletim sisteminin zamanlayabileceği en küçük yürütme birimidir; bir process içinde çalışır ve o process'in belleğini diğer thread'lerle paylaşır.
Thread sayfasını okuCoroutine
Coroutine, yarıda duraklayıp kontrolü bırakabilen ve sonra kaldığı yerden devam eden bir fonksiyondur; böylece birçok görev birkaç thread'i sırayla paylaşır.
Coroutine sayfasını okuThread ve Coroutine karşılaştırması
| Özellik | Thread | Coroutine |
|---|---|---|
| Zamanlayan | İşletim sistemi çekirdeği (kernel) | Program: bir event loop ya da çalışma ortamının zamanlayıcısı |
| Geçiş | Kesintili (preemptive): herhangi bir komutta kesilebilir | İşbirlikçi (cooperative): yalnızca await ya da yield gibi duraklama noktalarında |
| Tanesi başına bellek | Kendi yığını; çoğu zaman bir megabayt ya da daha fazlası ayrılır | Duraklamalar arasında gereken durumu tutan küçük bir nesne |
| Kaç tane | Bellek ve geçiş maliyetleri ağırlaşmadan önce yüzlerce ya da binlerce | Bir avuç thread üzerinde binlerceden milyonlarcaya |
| Paralellik | Birkaç CPU çekirdeğinde paralel çalışır | Tek thread'de sırayla çalışır; ancak birkaç thread'e dağıtılınca paraleldir |
| Paylaşılan veri | Kilit gerekir, çünkü bir güncellemenin ortasında geçiş olabilir | Duraklamalar arasında güvenlidir; durum yine de bir await boyunca değişebilir |
| En uygun olduğu yer | CPU ağırlıklı işler ve async sürümü olmayan bloklayan çağrılar | Eşzamanlı çok sayıda bekleme: ağ çağrıları, zamanlayıcılar, akışlar |
| Örnekler | Python'da threading.Thread, Java'da Thread, C++'ta std::thread | Python'da async def, Kotlin'de suspend, C++20'de co_await, Lua'da coroutine.yield |
Fark, açıklamalı
Thread, işletim sisteminin zamanladığı bir yürütme birimidir: kendi yığını (stack) ve yazmaçları vardır, process'inin belleğini diğer thread'lerle paylaşır ve çok çekirdekli bir CPU'da onlarla paralel çalışabilir. Coroutine ise kendi belirlediği noktalarda yarıda duraklayabilen ve daha sonra yerel değişkenleri korunmuş olarak kaldığı yerden devam edebilen bir fonksiyondur. Coroutine'ler thread'lerin üzerinde çalışır: hazır coroutine'lerden hangisinin sıradaki olacağına programın içindeki bir zamanlayıcı (scheduler), çoğu zaman da bir event loop karar verir.
Asıl fark, geçişe kimin karar verdiğidir. Thread'ler kesintili (preemptive) olarak zamanlanır: işletim sistemi birini herhangi bir komutta durdurup başka birini çalıştırabilir; paylaşılan verinin kilit gerektirmesinin nedeni budur. Coroutine'ler ise işbirlikçi çoklu görev (cooperative multitasking) kullanır: her biri await ya da yield gibi bir duraklama noktasına varana kadar çalışır; bu yüzden bir geçiş yalnızca küçük bir durumu kaydeder ve işletim sisteminin yardımına ihtiyaç duymaz. Bir thread kendi yığını için çoğu zaman bir megabayt ya da daha fazla yer ayırır, coroutine ise duraklamalar arasında yalnızca ihtiyaç duyduğu durumu tutar; böylece binlerce thread yönetebilen bir program milyonlarca coroutine yönetebilir.
İkisi rakipten çok birer katmandır. Coroutine'ler, zamanının çoğunu bekleyerek geçiren binlerce ağ bağlantısını tutan bir sunucu gibi I/O ağırlıklı işlere uyar; thread'ler ise birkaç çekirdeği kullanması gereken CPU ağırlıklı işlere ve aksi hâlde bir coroutine'in thread'ini tıkayacak bloklayan çağrılara uyar. Birçok çalışma ortamı ikisini birleştirir: Kotlin coroutine'leri thread havuzlarına dağıtır, Python'ın asyncio'su bloklayan çağrıları asyncio.to_thread ile bir thread'e devreder; Java 21 ile kalıcı hâle gelen sanal thread'ler (virtual threads) de aynı ucuz geçişi sıradan thread API'siyle yazılmış koda getirir.
Sık yapılan bir yanlış, coroutine'lerin kodu paralel çalıştırdığı düşüncesidir. Tek bir thread üzerinde yalnızca sırayla çalışırlar; bu yüzden duraklamadan uzun süre CPU işi yapan bir coroutine, o thread'deki diğer bütün coroutine'leri bloke eder. Bir başkası, işbirlikçi geçişin bütün yarış durumlarını ortadan kaldırdığı düşüncesidir: iki duraklama arasındaki kod kesintiye uğramaz, ama paylaşılan durum bir await boyunca yine de değişebilir. Go'nun goroutine'leri ise adlarına rağmen daha çok hafif thread'ler gibi davranır; çünkü çalışma ortamı onları kesebilir ve çekirdekler arasında paralel çalıştırır.
Hangisini kullanmalısınız?
Thread şu durumlarda doğru seçim:
- İş CPU ağırlıklı ve birkaç çekirdeği kullanmalı.
- Asenkron sürümü olmayan, bloklayan kütüphaneleri çağırıyorsunuz.
- Uzun bir görev, hiç duraklamasa bile diğerlerini dondurmamalı.
Coroutine şu durumlarda doğru seçim:
- Program zamanının çoğunu ağı, diski ya da zamanlayıcıları bekleyerek geçiriyor.
- Açık bağlantılar gibi binlerce eşzamanlı göreve ihtiyacınız var.
- Geçişlerin yalnızca bilinen noktalarda olmasını, böylece daha az kodun kilit gerektirmesini istiyorsunuz.
- Diliniz ya da framework'ünüz, Python'ın asyncio'su ya da Kotlin'deki gibi async/await üzerine kurulu.
Thread'lerle ve coroutine'lerle üç adet bir saniyelik bekleme (Python)
import threading, time
def download(name):
time.sleep(1) # blocks this thread; the OS runs the others
print(f"{name}: done")
threads = [threading.Thread(target=download, args=(n,)) for n in "abc"]
for t in threads: t.start()
for t in threads: t.join() # 3 threads, about 1 second in totalimport asyncio
async def download(name):
await asyncio.sleep(1) # pauses here; the event loop runs the others
print(f"{name}: done")
async def main():
await asyncio.gather(*(download(n) for n in "abc"))
asyncio.run(main()) # 1 thread, about 1 second in totalSık sorulan sorular
Coroutine'ler thread'lerden daha mı hızlıdır?
Eşzamanlı çok sayıda bekleme için evet: coroutine oluşturmak ve aralarında geçiş yapmak çok daha ucuzdur ve her biri çok daha az bellek kullanır. CPU ağırlıklı işlerde ise daha hızlı değildirler, çünkü tek bir thread üzerindeki coroutine'ler paralel çalışmaz.
Coroutine'ler birden fazla thread üzerinde çalışabilir mi?
Evet. Birçok çalışma ortamı coroutine'leri bir thread havuzuna dağıtır; örneğin Kotlin onları thread havuzlarıyla desteklenen dispatcher'larda çalıştırır. Tek bir coroutine yine de aynı anda yalnızca bir thread'de çalışır, ama bir duraklamadan sonra farklı bir thread'de devam edebilir.
Goroutine'ler thread mi, coroutine mi?
İkisinin arasında bir şey. Goroutine'ler Go çalışma ortamının yönettiği hafif thread'lerdir: coroutine'ler gibi oluşturması ucuzdur, ama çalışma ortamı onları kesebilir ve çekirdekler arasında paralel çalıştırır; bu yüzden paylaşılan veri yine de senkronizasyon gerektirir.