Ders 04 / 10
Eşzamansız Programlama
Örtüşmenin kaynağı işletim sistemi değil kodun kendisidir: her adımda denetimi bırakan eş yordam 80 teslimle 31 tik ve 49 örtüşen adım verir, yalnız girdi/çıktı adımında bırakan 54 teslimle 36 tik ve 44 örtüşen adım, hiç bırakmayan 0 teslimle 80 tik ve 0 örtüşen adım.
İçindekiler
Önceki iki ders örtüşmeyi hep dışarıdan aldı. İş parçacığı işletim sisteminden işçi istedi, süreç ayrı bir yorumlayıcıdan yuva istedi. İkisinde de hangi görevin ne zaman ilerleyeceğine karar veren şey kodun dışındaydı; program yalnız kaç akış istediğini söyledi.
Üçüncü bir yol var ve bu ders onu ölçer. Eşzamansız programlamada örtüşmenin kaynağı ne işletim sistemidir ne de ikinci bir yorumlayıcı: kodun kendisidir. Bir eş yordam denetimi bırakacağı yeri kendi yazar. Bu dersin sorusu bu yüzden dar ve sayılabilir: bir eş yordam denetimi ne sıklıkla bırakırsa ne kadar örtüşme çıkar, ve hiç bırakmazsa ne olur?
Eş Yordam Sürdürülebilir Bir Gövdedir
Eş yordam (coroutine), çağrıldığında gövdesi koşmayan, çağrıya karşılık bir nesne
döndüren ve sürdürüldükçe ilerleyen bir işlevdir. async def ile tanımlanır. Gövdesindeki
await, bir sonucu bekleyen bir işaret değil, denetimi bırakma noktasıdır: eş yordam o
noktada durur, kendini çağıran tarafa döner ve başka bir şeyin ilerlemesine izin verir.
Duraklayan gövde bu kursta ilk kez geçmiyor. Veri Yapıları ve Fonksiyonel Araçlar kursu
üreteci ölçtü ve gövdenin yield’de durup yerel adlarını koruduğunu gösterdi; o ölçüm burada
tekrarlanmaz. Aradaki fark tek cümlede yazılabilir: üreteç bir değer vermek için durur, eş
yordam denetimi bırakmak için durur — birinin duruşu akışa bir öğe ekler, öbürünün duruşu
başkasına sıra verir.
Olay döngüsü (event loop) bu duruşları yöneten taraftır. Elinde sürdürülmeyi bekleyen eş yordamlar vardır; birini sürdürür, o bir teslim noktasına gelip döndüğünde sıradakini sürdürür. Bir eş yordamı döngüye kaydedip bağımsız ilerlemesini istemek, onu bir görev yapmaktır.
Olay Döngüsü Yeni Bir Yuva Getirmez
Modelin diliyle söylenirse: eşzamansız rejim ne işçi sayısını ne yuva sayısını işletim sisteminden ister. Olay döngüsü tek bir akışta koşar; aynı anda tek bir gövde ilerler ve yuva birdir.
O zaman örtüşme nereden gelir? Tek bir yerden: bir gövde denetimi bıraktığında öbürünün ilerleyebilmesinden. Bu kursun ölçüsüyle, teslim noktası olmadan örtüşen adım da yoktur. Buradan doğrudan bir üst sınır çıkıyor ve ölçüm onu sınayacak: örtüşen adım, teslim sayısını geçemez.
Bunun tersi de var ve dersin sonunda ölçülüyor: teslim etmek örtüşme için gerekli ama yeterli değil. Bırakılan denetimi alacak, o anda hazır bekleyen ikinci bir görev de olmak zorunda.
Ölçüm Kendi Döngüsünü Yazıyor
Ölçümün çizelgeleyicisi burada elle yazılmış bir olay döngüsüdür; eş yordamlar ise gerçektir.
async def ile tanımlanmış, gerçek await içeren gövdeler, gerçek eş yordam nesneleri
üretiyor ve send ile sürdürülüyor. Değiştirilen tek şey kimin sürdürdüğü: standart olay
döngüsü yerine sırası yazılı bir döngü. Sebebi ölçümün koşulu — hangi görevin hangi tikte
sürdürüldüğü sabit olmazsa örtüşen adım sayılamaz.
Dersin sonundaki kısa blok gerçek olay döngüsünü çalıştırıyor ve sırayı await’in kendisi
sabitliyor.
Ölçümün varsayımları:
- EM36 — Görev kurgusu önceki derslerin aynısıdır: sekiz görev, görev başına on adım, toplam seksen adım, girdi/çıktı ağırlıklı yük.
- EM37 — Eş yordamlar gerçektir;
async defile tanımlanır veawaitile durur. Onları sürdüren döngü ders içinde yazılmıştır. - EM38 — Teslim noktası, eş yordamın olay döngüsüne dönüş sayısıdır; her
awaitbir teslimdir. - EM39 — Üç rejim yalnız teslim noktalarının yerinde ayrılır: her adımda, yalnız girdi/çıktı adımında, hiç. Adım dizileri üçünde de aynıdır.
- EM40 — Olay döngüsü tek akıştır: işlemci yuvası birdir ve değişmez.
- EM41 — Bir eş yordam denetimi bırakmadan
kadım atarsa döngü okadım boyunca meşguldür; ilk adım sürdürüldüğü tike yazılır, kalank-1adım kimsenin ilerlemediği ayrı tiklere. - EM42 — Sürdürme sırası görev indisine göredir ve koşum boyunca değişmez.
- EM43 — Karşılaştırma tabanı önceki derslerin çizelgeleyicisidir: aynı görevler, sekiz işçi, tek yuva.
- EM44 — Gerçek olay döngüsü yalnız sıralı
awaitile çalıştırılır; çıktı sırasıawait’in kendisiyle sabitlenir, çizelgeleyiciye bırakılmaz. - EM45 —
sleep(0)bu ölçümde bir bekleme değil, bir teslim işaretidir; sayılan şey süre değil, denetimin kaç kez bırakıldığıdır. - EM46 — Süre ölçülmez; sayılan birim adım, tik, örtüşen adım ve teslimdir.
- EM47 — Ölçüm tek koşumdur ve tohum sabittir.
Ölçüm
"""Es yordam: denetimi kim, ne siklikla birakiyor.""" import asyncio TOHUM = 20260817 ISLEMCI, GC = "islemci", "g_c" def uretec(tohum): d = tohum % 2147483646 + 1 def r(n): nonlocal d d = (d * 48271) % 2147483647 return d % n return r def gorevler(sayi=8, adim=10, gc_payi=7, tohum=TOHUM): """Her gorev bir adim dizisi; gc_payi/10 orani girdi/cikti adimi.""" r, liste = uretec(tohum), [] for i in range(sayi): liste.append([GC if r(10) < gc_payi else ISLEMCI for _ in range(adim)]) return liste def kosum(gorevler, isci, islemci_yuvasi): """Onceki derslerin cizelgeleyicisi; karsilastirma tabani.""" kalan = [list(g) for g in gorevler] tik = ortusen = 0 while any(kalan): etkin = [i for i, g in enumerate(kalan) if g][:isci] if not etkin: break yuva, ilerleyen = islemci_yuvasi, 0 for i in etkin: if kalan[i][0] == ISLEMCI: if yuva <= 0: continue yuva -= 1 kalan[i].pop(0) ilerleyen += 1 tik += 1 ortusen += max(0, ilerleyen - 1) return tik, ortusen class Bekleyis: """Es yordamin denetimi olay dongusune biraktigi nokta.""" def __init__(self, tur): self.tur = tur def __await__(self): return (yield self.tur) async def her_adimda(adimlar, gunluk): """Her adimdan once denetimi birakir: teslim noktasi = adim sayisi.""" for a in adimlar: await Bekleyis(a) gunluk.append(a) async def gc_adiminda(adimlar, gunluk): """Yalniz girdi/cikti adiminda birakir; islemci adimlarini pes pese atar.""" for a in adimlar: if a == GC: await Bekleyis(a) gunluk.append(a) async def hic(adimlar, gunluk): """Denetimi hic birakmaz.""" for a in adimlar: gunluk.append(a) def dongu(yapici, gorevler, islemci_yuvasi=1): """El yazimi belirlenimci olay dongusu. Donen: tik, ortusen, teslim. Bir es yordam denetimi birakmadan k adim atarsa dongu o k adim boyunca mesguldur: borc olarak k-1 tik daha yazilir ve o tiklerde kimse ilerlemez. """ gunluk = [[] for _ in gorevler] coro = [yapici(g, gunluk[i]) for i, g in enumerate(gorevler)] istek = [None] * len(coro) canli = [True] * len(coro) borc = [0] * len(coro) teslim = 0 def surdur(i, pesin=0): nonlocal teslim onceki = len(gunluk[i]) try: istek[i] = coro[i].send(None) teslim += 1 except StopIteration: istek[i] = None canli[i] = False borc[i] += max(0, len(gunluk[i]) - onceki - pesin) for i in range(len(coro)): surdur(i) # ilk teslim noktasina kadar tik = ortusen = 0 while any(canli) or any(borc): mesgul = [i for i in range(len(coro)) if borc[i] > 0] if mesgul: borc[mesgul[0]] -= 1 tik += 1 continue yuva, ilerleyen = islemci_yuvasi, 0 for i in range(len(coro)): if not canli[i] or istek[i] is None: continue if istek[i] == ISLEMCI: if yuva <= 0: continue yuva -= 1 surdur(i, 1) ilerleyen += 1 if ilerleyen == 0: break tik += 1 ortusen += max(0, ilerleyen - 1) return tik, ortusen, teslim g = gorevler(gc_payi=7) print("girdi/çıktı ağırlıklı yük, seksen adım, tek yuva") print(f"{'teslim noktası':<24s} {'teslim':>7s} {'tik':>5s} {'örtüşen':>8s}") for ad, yapici in (("her adımda", her_adimda), ("yalnız g/ç adımında", gc_adiminda), ("hiç", hic)): tik, ortusen, teslim = dongu(yapici, g) print(f"{ad:<24s} {teslim:7d} {tik:5d} {ortusen:8d}") tik, ortusen = kosum(g, 8, 1) print(f"{'önceki dersin çizelgesi':<24s} {'':>7s} {tik:5d} {ortusen:8d}") async def basamak(ad, kere, gunluk): """Her yinelemede denetimi olay dongusune birakir.""" for i in range(kere): await asyncio.sleep(0) gunluk.append(f"{ad}{i}") async def sirali(): """Sirali await: ikinci es yordam birincisi bitmeden baslamaz.""" gunluk = [] for ad in ("a", "b"): await basamak(ad, 3, gunluk) return gunluk print() gunluk = asyncio.run(sirali()) print("gerçek olay döngüsü, sıralı await:", " ".join(gunluk)) print(f"teslim noktası {len(gunluk)}, örtüşen adım 0 — " f"bırakan var, alan yok")
girdi/çıktı ağırlıklı yük, seksen adım, tek yuva teslim noktası teslim tik örtüşen her adımda 80 31 49 yalnız g/ç adımında 54 36 44 hiç 0 80 0 önceki dersin çizelgesi 31 49 gerçek olay döngüsü, sıralı await: a0 a1 a2 b0 b1 b2 teslim noktası 6, örtüşen adım 0 — bırakan var, alan yok
Teslim Sıklığı Örtüşmeyi Belirliyor
Tablo üç satırda tek bir değişkeni değiştiriyor: await’in nereye yazıldığı. Adım dizileri
üçünde de aynı, görev sayısı aynı, yuva aynı.
Her adımda teslim eden eş yordam 80 kez denetimi bırakıyor ve seksen adımı 31 tikte bitiriyor; örtüşen adım 49.
Yalnız girdi/çıktı adımında teslim eden eş yordam 54 kez bırakıyor — girdi/çıktı adımı sayısı kadar. Tik 36’ya çıkıyor, örtüşen adım 44’e iniyor. Beş tiklik kayıp, işlemci adımlarının pes peşe atılmasından geliyor: eş yordam iki girdi/çıktı adımı arasında üç işlemci adımı taşıyorsa, o üç adımı tek bir sürdürmede yapar ve döngü o sırada kimseye sıra veremez.
Hiç teslim etmeyen eş yordam 0 kez bırakıyor ve sonuç 80 tik, 0 örtüşen adım —
tek akış rejiminin tam olarak kendisi. async def ile yazılmış olması hiçbir şey değiştirmiyor.
Üç satırın verdiği sıralama şu: teslim 80 → 54 → 0, örtüşen adım 49 → 44 → 0. İlişki tek yönlü ve üst sınır her satırda korunuyor: örtüşen adım teslim sayısını hiç geçmiyor.
Buradan doğrudan bir tasarım sonucu çıkıyor: eşzamansız bir programda örtüşmeyi belirleyen
şey kaç görev başlatıldığı değil, gövdelerin nerede durduğudur. async sözcüğü bir
teslim noktası üretmez; await üretir.
İkinci satırın beş tiklik kaybı ayrıca okunmayı hak ediyor, çünkü gerçek gövdelerin çoğu o satıra benzer. Kayıp, işlemci adımlarının uzunluğuyla büyür: iki teslim noktası arasında ne kadar çok adım varsa döngü o kadar uzun meşgul kalır. Bu, eşzamansız kodda uzun hesapların neden ayrı bir yere taşındığının sayılabilir gerekçesidir — taşınmadıklarında bütün görevleri birden bekletirler.
Aynı Çizelge, Başka Kaynak
Tablonun son satırı bir doğrulama taşıyor ve dersin en dikkate değer sayısı orada.
Önceki derslerin çizelgeleyicisi, aynı görevleri sekiz işçi ve tek yuvayla koşturunca 31 tik / 49 örtüşen adım veriyordu. Her adımda teslim eden eş yordam rejimi de 31 tik / 49 örtüşen adım veriyor. İki sayı birebir aynı.
Aynılık bir tesadüf değil, tanımın sonucu. İş parçacığı rejiminde bir görev ilerledikten sonra sıra ötekine geçiyordu; kararı çizelgeleyici veriyordu. Eş yordam rejiminde de bir görev bir adım atıp duruyor ve sıra ötekine geçiyor; kararı kod veriyor. Görülen çizelge aynı, çizelgeyi yazan taraf başka.
Bu, eşzamansız programlamanın ne kazandırdığını da yerine koyuyor. Kazandırdığı şey daha çok örtüşme değil — üst satır bunu gösteriyor, sayı aynı. Kazandırdığı şey örtüşmenin kaynağı: işletim sisteminden iş parçacığı istemeden, tek akışta, bırakma noktalarını kodda yazarak aynı çizelgeyi elde etmek. Bedeli de aynı yerde: bırakma noktalarını yazmayı unutan gövde, üçüncü satıra düşer.
Bırakan Var, Alan Yok
Son iki satır teslim noktasının yetmediğini gösteriyor.
Gerçek olay döngüsünde koşan iki eş yordam var; her biri üç kez denetimi bırakıyor, toplam
6 teslim noktası. Çıktı a0 a1 a2 b0 b1 b2. İkinci eş yordam birincisi bitmeden hiç
ilerlemiyor ve örtüşen adım 0.
Sebebi await’in ne yaptığı: sirali gövdesi basamak("a", ...) üzerinde bekliyor ve o
bitmeden basamak("b", ...) başlatılmamış oluyor. Denetim bırakılıyor, ama alacak hazır
bir görev yok; döngü aynı eş yordamı geri sürdürüyor.
Buradan iki koşullu bir kural çıkıyor. Örtüşme için hem gövdenin denetimi bırakması hem
de o anda ilerlemeye hazır ikinci bir görevin bulunması gerekir. Birincisi await ile,
ikincisi eş yordamları döngüye görev olarak kaydetmekle sağlanır. İkisinden biri eksikse
tablonun üçüncü satırındaki sonuç çıkar: teslim sayısı kaç olursa olsun, örtüşen adım sıfır.
Bu ayrım, eşzamansız kodda en sık yapılan hatanın da açıklamasıdır: gövde async yazılmış,
await konmuş, hiçbir şey örtüşmemiştir. Ölçülmesi gereken sayı await sayısı değil,
aynı anda ilerlemeye hazır görev sayısıdır.
Teslimin Modelde Sayılmayan Bedeli
Tablonun ilk satırı en çok teslimi ve en çok örtüşmeyi birlikte veriyor; buradan “ne kadar
çok await o kadar iyi” sonucu çıkarılabilir. Model bunu doğrular ama model bir şeyi
saymıyor ve bunu yazmak gerekir.
Her teslim bir defter işidir: eş yordamın durumu saklanır, döngünün sırasına geri konur, sonra yeniden sürdürülür. Modelde bu işin bedeli sıfır sayılıyor; sayılan tek şey adımın hangi tikte atıldığı. Gerçek bir programda teslim bedelsiz değildir ve her adımda teslim eden bir gövde, kazandığı örtüşmenin bir kısmını deftere geri öder.
Bunun ölçüme etkisi yönlüdür ve okunabilir: 31 tik, eşzamansız rejimden beklenebilecek kazancın üst sınırıdır. Bedel eklendiğinde bu sayı yalnız büyür. İkinci satırdaki 36 ise iki bedelin arasında duruyor: daha az teslim yazıyor ama işlemci adımlarını pes peşe attığı için beş tik kaybediyor.
Pratikte yazılan gövdeler ikinci satıra benzer, çünkü await bekleyen çağrıların önüne
yazılır, hesap yapan satırların arasına değil. Ölçümün söylediği şey şu: ikinci satır ile
üçüncü satır arasındaki fark — 44 örtüşen adıma karşı 0 — hesap yapan satırların
uzunluğuna değil, aradaki bekleyen çağrının denetimi bırakıp bırakmadığına bağlı. Uzun bir
işlemci bölümü örtüşmeyi biraz azaltır; bırakmayan tek bir çağrı onu sıfırlar.
Özet
- Eş yordam, çağrıldığında koşmayan ve sürdürüldükçe ilerleyen bir gövdedir;
awaitbir bekleme değil, denetimin bırakıldığı teslim noktasıdır. - Olay döngüsü ne işçi ne yuva ekler; tek akışta koşar. Örtüşmenin tek kaynağı gövdelerin denetimi bırakmasıdır ve örtüşen adım teslim sayısını geçemez.
- Aynı seksen adım, teslim her adımda yazıldığında 80 teslim / 31 tik / 49 örtüşen adım, yalnız girdi/çıktı adımında yazıldığında 54 / 36 / 44, hiç yazılmadığında 0 / 80 / 0 verir.
- Her adımda teslim eden eş yordam rejimi, sekiz işçili tek yuvalı çizelgeyle birebir aynı sayıyı verir; değişen şey çizelge değil, çizelgeyi yazan taraftır.
- Teslim noktası örtüşme için gerekli ama yeterli değildir: sıralı
awaitile koşan iki eş yordam 6 kez denetimi bırakır ve 0 örtüşen adım verir.
Sonraki Adım
Tablonun üçüncü satırı bu dersin sonucu ama bir sonraki dersin sorusu. Gövdenin denetimi hiç bırakmaması her zaman yazarın tercihi değildir: çağrılan bir işlev bırakmayı bilmiyorsa gövde onu bekler ve döngü durur. Sonraki ders bunu ölçer — akıştaki bir çağrı denetimi bırakmadığında örtüşen adım kaç düşer, bütün çağrılar bırakmadığında ne kalır, ve bırakmayı bilmeyen bir çağrıyı akışa geri sokmanın bedeli kaç adımdır?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.