Kısım I · Makine · Bölüm 14
Süreçler ve İş Parçacıkları
İşlemci zamanı, ortak bellek ve işler arasındaki sıra (Processes, threads, scheduling)
Neden burada: İşletim sistemi kaynakları yönetiyor dedik; şimdi ilk somut kaynağı açıyoruz: işlemciyi birden çok çalışan işe kullandırmak. Bu paylaşım, aynı belleği paylaşan işler çoğalınca doğruluk sorunları da çıkarır.
Neye dayanıyor: Bölüm 13: çekirdek, kullanıcı alanı, sistem çağrısı ve kaynak yönetimi; Bölüm 6: program sayacı, yazmaçlar ve çok çekirdek; Bölüm 10: çağrı yığını ve ortak adresteki değer; Bölüm 11: kuyruk ve verinin düzenlenmesi. Bu fikirleri yeniden uzun uzun anlatmadan çalışan işlere bağlayacağız.
- Kat 10Bizim hukuk uygulamamızSorular, puan, seri ve sıralama: üstteki amaç.
- Kat 9Tarayıcı ve webHTML, CSS, JavaScript: Bölüm 20, 23–26.
- Kat 8Ağ ve internetMakineler arasında iletişim: Bölüm 19–20.
- Kat 7İşletim sistemiKaynaklar, süreçler, bellek ve dosyalar: Bölüm 13–18.
- Kat 6Gömülü yazılım ve açılışİlk koddan işletim sistemine geçiş: Bölüm 12.
- Kat 5Diller, derleyici ve veri düzeniC ile tarif, adres ve algoritma: Bölüm 9–11.
- Kat 4Makine dili ve assemblyBitlerden oluşan işlemci komutları: Bölüm 7.
- Kat 3İşlemci ve bellekKomutları yürüten donanım ve RAM: Bölüm 6, 8.
Bölüm sonunda program dosyası ile süreci ayırabilecek, durdurulan işin nasıl devam ettiğini açıklayabilecek ve iş parçacığı ile goroutine’i aynı nesne sanmayacaksın. Ayrıca tek çekirdekte bile yanlış puan doğurabilen bir erişim sırasını kendi elinle izleyebileceksin.
14.1 Program ile süreç: tarif ve sürmekte olan iş
Bölüm 9.1'de derlemenin sonunda diskte bir çalıştırılabilir dosya oluştuğunu gördün. O dosya orada dururken henüz hesap yapmıyor. Süreç (process), bir programın çalışmakta olan örneği ve o çalışmaya ait durumdur. Program komutların tarifi; süreç o tarifin belleği, yürütme konumu ve kaynaklarıyla birlikte sürmekte olan halidir. OSTEP bu ayrımı işletim sisteminin temel soyutlamalarından biri olarak kurar.1
Aynı çalıştırılabilir dosyadan iki süreç başlatılabilir. Komutların tarifi aynı olabilir; ama biri başka girdiyi, diğeri başka girdiyi işler. İkisinin o anki hesap değerleri ve sonraki komut konumları aynı olmak zorunda değildir. Bir yemek tarifiyle o tariften ayrı tencerelerde yapılan iki yemeğin ayrımı gibi: tarif kopyalamak ile pişirmeye başlamak ayrı olaylardır. Benzetmedeki tencerenin bilgisayardaki karşılığı tek başına RAM değildir; süreç durumunun tamamını düşünmelisin.1
Bir sürecin “durumu” neleri içerir?
- Bellek görünümü: Komutlar, veriler, öbek ve yığın gibi çalışma bölgeleri. Adres alanının nasıl korunduğunu Bölüm 15 açacak.
- Yürütme konumu: Bölüm 6'daki program sayacı, yani nereden devam edileceği.
- İşlemci değerleri: Hesapta kullanılan yazmaçların durumu ve yığın işaretçisi gibi bilgiler.
- İşletim sistemi kayıtları: Açık dosyalar, çalışma durumu ve süreci tanıyacak kimlik gibi yönetim bilgileri.
OSTEP'in süreç tanımı bu parçalardan oluşur. “Çalışıyor” demek, her an CPU'da komut yürütüyor demek değildir. Süreç birazdan göreceğimiz gibi bekliyor da olabilir; var olmaya devam eder ve sonraki fırsatta çalışmasını sürdürür. Donanımın yalnızca bir anına bakmakla bir işin tüm yaşam süresine bakmak farklıdır.1
PID: çalışan örneğin numarası
Süreç kimliği (process identifier, PID), işletim sisteminin bir süreci ayırt etmek için verdiği sayısal kimliktir. Microsoft belgeleri, aynı anda çalışan süreçleri bu numarayla tanımlamayı anlatır. Aynı program adı altında birden fazla süreç bulunabileceği için yalnızca ada bakmak yeterli olmayabilir. PID'yi uygulamadaki kullanıcı kimliği veya bir veritabanı kaydının kalıcı numarası sanma; farklı kavramlardır.6
macOS'taki Etkinlik Monitörü (Activity Monitor), süreçlerin bellek ve CPU kullanımını gösterir; listede uygulamalar kadar görünür penceresi olmayan arka plan süreçleri de bulunabilir. Windows'taki Görev Yöneticisi (Task Manager) de süreçleri ve PID sütununu incelemeye yarar. Görünen bir pencere ile tek süreç arasında zorunlu birebir ilişki yoktur. Bu araçlara “bilgisayarın çalışan işler listesi” diye bak; yalnızca açık pencere listesi değiller.56
Go ile yazılacak arka ucun bir çalışan örneğini düşün. Aynı süreç birçok kullanıcının isteğini ele alabilir; her kullanıcının oturum açması otomatik olarak yeni işletim sistemi süreci doğurmaz. Uygulamadaki “Kerem adlı kullanıcı”, “giriş oturumu” ve “sunucu programının çalışan örneği” ayrı nesnelerdir. Birden fazla sunucu örneği çalıştırmaya da karar verilebilir; o zaman ayrı süreçler vardır. Bunlar tasarım olasılıklarıdır, görmediğimiz prototipin iç mimarisi hakkında keşif değil.18
14.2 Bağlam değişimi, zamanlayıcı ve zaman dilimi
Tek işlemciyi düşünmek işi kolaylaştırır: şu anda A programının komutları yürütülüyor. B de hazır. A'yı yok etmeden B'ye nasıl geçeriz? Önce A'nın nerede kaldığını saklar, sonra B'nin saklanmış durumunu işlemciye geri koyarız. Buna bağlam değişimi (context switch) denir. Bağlam, buradaki yürütmenin devam etmesi için gereken yazmaç ve kontrol durumudur; “konu değişikliği” anlamında gevşek bir benzetme değil, gerçek bir saklama ve geri yükleme işidir.2
Neyi saklıyoruz, neyi baştan başlatmıyoruz?
Program sayacı geri konduğunda sıradaki komut belli olur. Yazmaçlar geri konduğunda yarım kalan hesabın değerleri korunur. Yığın işaretçisi geri konduğunda fonksiyon çağrılarının hangi çerçevesinde olunduğu bilinir. Bittiğinde A yeniden main'in başından başlamaz; kaldığı yerden devam eder. Süreçler arası geçişte bellek görünümünün seçimi de önemlidir; bu kısmı Bölüm 15'e bırakıyoruz.12
Burada “bütün RAM her geçişte diske kopyalanır” diye bir işlem yoktur. OSTEP'in çizdiği temel mekanizma, gereken yazmaçların ve çekirdek bağlamının bellekteki yönetim kayıtlarıyla saklanıp geri getirilmesidir. Geçişin bir bedeli vardır; ayrıca önceki işin sıcak önbellek bilgileri yeni iş için aynı derecede yararlı olmayabilir. Bu yüzden işleri ne kadar sık böleceğimiz bir performans kararıdır.23
İşletim sistemi kontrolü nasıl geri alır?
A, dosya okuma gibi bir sistem çağrısıyla çekirdeğe girdiğinde işletim sistemi yeniden çalışabilir. Ama A hiç çağrı yapmadan sonsuz döngüye girerse? Yalnızca programın iyi niyetine güvenen bir düzen yetersiz kalır. OSTEP'in anlattığı donanım desteği zamanlayıcı kesmesidir (timer interrupt): zamanlayıcı donanımı bir olay üretir, işlemci tanımlı çekirdek koduna geçer ve işletim sistemi kontrolü geri alır. Genel kesme ve aygıt ayrıntısı Bölüm 17.3'te.2
Kontrolü geri almakla başka iş seçmek aynı adım değildir. İşletim sistemi bazen aynı işi sürdürür. Başka iş seçecekse kararı zamanlayıcı (scheduler) verir; geçiş mekanizması bu kararı gerçekler. Bölüm 13'teki politika/mekanizma ayrımını şimdi görebilirsin: “sırada kim var?” politika; “durumu nasıl saklayıp geri koyarım?” mekanizmadır.2
Hazır, çalışıyor, bekliyor
OSTEP başlangıç için üç durum ayırır: hazır (ready), çalışıyor (running) ve bekliyor (blocked). Hazır işin yürütülebilecek komutu vardır ama şu anda işlemci ona verilmemiştir. Çalışan iş işlemcide ilerler. Bekleyen iş ise disk sonucunun gelmesi gibi bir olay olmadan devam edemez. Bekleyen işi sırf “sırası geldi” diye çalıştırmak, eksik sonucu var etmez.1
hazır ── seçilir ──► çalışıyor
▲ │
└── işlemci bırakılır ─┘
│ beklemesi gereken iş başlar
▼
bekliyor
│ gereken olay tamamlanır
└─────────► hazır
Bekleme bitince iş tekrar hazır olabilir; fakat bu, aynı anda kesin işlemci aldığı anlamına gelmez. “Hazır” ile “çalışıyor” ayrımı bu yüzden var. Bir uygulamanın toplam geçen süresi yalnızca kendi hesap süresi değildir: kaynak beklemesi, giriş/çıkış beklemesi ve sıra beklemesi de olabilir. OSTEP'in süreç izlerinde CPU bir işin bekleme zamanını diğer işi ilerletmek için kullanır.1
Zaman dilimi: kısa tur mu, uzun tur mu?
Zaman dilimi (time slice, scheduling quantum), bir işin yeniden değerlendirilmeden önce çalışmasına ayrılan zaman bütçesidir. OSTEP'in dönüşümlü zamanlama (round-robin scheduling) örneği, hazır işlere sırayla bir dilim verip sonra sıradakine geçer. Bu öğretici bir politikadır; bütün modern sistemlerin tek ve değişmez gerçek politikası olduğunu söylemiyoruz. İş beklemeye geçebilir veya bitebilir; dilimin tamamını tüketmek zorunda değildir.3
Öğretici bir hesap yapalım: üç işin her biri 6 milisaniyelik CPU işi istesin, hepsi aynı anda hazır olsun, dilim 2 milisaniye olsun. Geçiş maliyetini bu ilk çizimde sıfır kabul ediyoruz. Sıra aşağıdaki gibi olur; Python ile simüle edildi. Bunlar makinenin ölçülmüş değerleri değil, varsayımlı modeldir.
Zaman (ms): 0–2 2–4 4–6 6–8 8–10 10–12 12–14 14–16 16–18
Çalışan: A B C A B C A B C
İlk çalışma zamanı: A=0, B=2, C=4 ms
Bitiş zamanı: A=14, B=16, C=18 ms
A'yı bitirip sonra B'ye, ardından C'ye geçseydik ilk çalışma zamanları 0, 6, 12 milisaniye olurdu. Dönüşümlü model ilk fırsatı daha erken paylaştırır; bu, her işin bitişinin daha erken olduğu anlamına gelmez. OSTEP bu farkı ilk kez çalıştırılmaya kadar geçen yanıt süresi (response time) ile tamamlanma süresi üzerinden tartışır. Kullanıcıya hızlı ilk tepki ile bir işi çabuk bitirmek bazen farklı hedeflerdir.3
Gerçek geçişin maliyetini de hayalî bir modelle görünür yapalım. Her 2 milisaniye yararlı işten sonra 0,1 milisaniye yönetim işi olsa bir turun maliyet payı 0,1 / 2,1 ≈ %4,76 olur. Dilim 10 milisaniye olsaydı 0,1 / 10,1 ≈ %0,99 olurdu. Bunlar Python ile hesaplanmış oranlar; herhangi bir işletim sisteminin ölçümü değildir. Uzun dilim sabit yönetim bedelini seyrek ödetir, ama sıradaki işin ilk fırsatını geciktirebilir. “En küçük dilim her zaman en iyi” diyemeyiz.3
14.3 İş parçacığı: aynı süreçte birden çok yürütme yolu
Bir süreci şimdiye kadar tek yürütme konumuyla düşündük. İş parçacığı (thread), bir sürecin içindeki yürütme akışıdır. Çok iş parçacıklı bir süreçte birden çok program sayacı ve yürütme durumu vardır. Her iş parçacığının kendi yazmaç durumu ve çağrı yığını bulunur; ama aynı süreçteki iş parçacıkları adres alanını, kodu ve ortak verileri paylaşır. OSTEP'in önemli ayrımı budur: yeni iş parçacığı, tamamen ayrı özel bellek dünyası olan yeni süreç demek değildir.4
| Özellik | Ayrı süreçler | Aynı sürecin iş parçacıkları |
|---|---|---|
| Adres alanı | Normal modelde ayrı; paylaşım ayrıca düzenlenir. | Aynı adres alanı içinde çalışırlar. |
| Program sayacı ve yazmaç durumu | Yürütmenin durumunu ayrı tutmak gerekir. | Her iş parçacığında ayrıdır. |
| Fonksiyon çağrı yığını | Ayrı yürütmelerin kendi çağrı durumu vardır. | Her iş parçacığının kendi yığını bulunur. |
| Ortak veriye erişim | Arada tanımlı iletişim veya paylaşım gerekir. | Aynı bellekteki veriye erişmek kolaydır; eşgüdüm gerekir. |
“Yığınları ayrı” demek, iş parçacığının yığınının öteki iş parçacıklarına karşı süreç benzeri bir koruma duvarıyla çevrildiği anlamına gelmez. Aynı adres alanında yaşarlar; bir adrese başvuru başka iş parçacığına verilebilir. Ayrı yığın, ayrı fonksiyon çağrı geçmişi demektir; ayrı güvenlik evreni değil. Ortak adres alanı, işbirliğini kolaylaştırırken hatanın paylaşılmasını da mümkün kılar.4
Neden iş parçacığı ekleriz?
İki ayrı gerekçe var. Birincisi hesap işini gerçekten birden çok işlemciye/çekirdeğe dağıtabilmektir. İkincisi ise bir akış yavaş giriş/çıkış beklerken başka akışın ilerlemesidir. OSTEP bu iki nedeni ayrı anlatır. Bir dosya sonucunu bekleyen iş parçacığı varken programın başka işi ele alabilmesi, hesapları aynı anda iki çekirdekte yürütmüş olduğumuzu kanıtlamaz. Beklemeyi örtüşmek ile hesapları çoğaltmak farklı kazançlardır.4
Eşzamanlılık (concurrency), birden çok işin ilerlemesini birlikte yönetmektir. Paralellik (parallelism) ise birden çok işin gerçekten aynı anda yürütülmesidir. Tek yürütme imkânıyla A-B-A-B biçiminde ilerlemek eşzamanlıdır; A ile B'nin ayrı çekirdeklerde aynı anda ilerlemesi paraleldir. Birincisi programın yapısı ve eşgüdümüyle, ikincisi o anda kullanılan fiziksel yürütme imkânıyla ilgilidir.48
Tek yürütme imkânı, zaman soldan sağa:
CPU: A A | B B | A A | B B
İki bağımsız yürütme imkânı:
çekirdek1: A A A A
çekirdek2: B B B B
İlk satırda birden çok iş vardır ama aynı anda tek akış komut yürütür. İkinci çizimde gerçek örtüşme vardır. Şema sürelere dair ölçüm değil, kavram çizimidir. Çok sayıda iş parçacığı oluşturmak da tek başına ikinci çizimi garanti etmez: gereken donanım, hazır işler ve paylaşım düzeni gerekir. Hepsi aynı kilidi bekliyorsa ek iş parçacığı eklemek yararlı işi artırmayabilir.49
Bu noktada zamanlayıcı tanımımızı keskinleştirelim: modern çok iş parçacıklı sistemlerde işletim sistemi iş parçacıklarını da zamanlar. Az önce “A süreci çalışıyor” diye çizdiğimiz resim, tek iş parçacıklı süreçler için başlangıç modeliydi. İş parçacıkları arasında geçerken de yazmaç durumları değişir; aynı sürecin iş parçacıkları arasında adres alanı aynı kalır. Donanımı paylaştırmanın temel fikri kaybolmadı, yürütme birimimiz ayrıntılandı.4
14.4 Yarış durumu ve kilit: doğru puanı korumak
Yarış durumu (race condition), sonucun işlerin istenmeyen bir sırada iç içe geçmesine bağlı olarak yanlış olabildiği durumdur. Aynı veriye erişen iş parçacıkları bunun klasik örneğini oluşturur. Bölüm 9'da puan = puan + 10 satırı tek bir fikir gibi görünüyordu. Bölüm 7'nin alt katında ise değeri yüklemek, toplamak ve geri yazmak ayrı adımlar olabilir. Araya başka bir iş girerse “bir satır” olması onu korumaz.4
Kaybolan güncellemeyi adım adım izle
Varsayalım ortak puan 100, iki bağımsız doğru cevap da 10'ar puan kazandırıyor. Doğru sonuç 120 olmalı. Aşağıdaki tablo, tanımlı bir okuma-hesaplama-yazma modelinin bilerek seçilmiş adım sırasıdır; gerçek bir güvensiz C programının her çalışmada vereceği sonucun kehaneti değil. Bu sırayı Python ile deterministik olarak oynattık.
| Adım | A'nın işi | B'nin işi | Ortak puan |
|---|---|---|---|
| 1 | 100'ü kendi geçici değerine okur. | — | 100 |
| 2 | — | 100'ü kendi geçici değerine okur. | 100 |
| 3 | Kendi değeriyle 100 + 10 = 110 hesaplar. | — | 100 |
| 4 | — | Kendi değeriyle 100 + 10 = 110 hesaplar. | 100 |
| 5 | 110'u ortak puana yazar. | — | 110 |
| 6 | — | 110'u ortak puana yazar. | 110 |
İki toplama da aritmetik olarak doğruydu; hata, ikisinin de aynı eski değere dayanmasıydı. Buna kaybolan güncelleme (lost update) denir. Üstelik tek çekirdekte de mümkündür: A okuduktan sonra durdurulup B çalıştırılabilir. Yarış için iki çekirdeğin tam aynı nanosaniyede yazması şart değildir. OSTEP'in kesmeyle bölünen sayaç örneği de aynı sorunu gösterir.4
Kilit neyi kapatır?
Kritik bölüm (critical section), ortak durum üzerinde birbirine karışmaması gereken adımların oluşturduğu parçadır. Burada yalnızca son yazma değil, eski puanı okuma → artırma → yazma bütünüdür. Kilit (lock), bu parçaya erişimi düzenleyen eşgüdüm aracıdır. Karşılıklı dışlama (mutual exclusion), ilgili bölümde aynı anda en fazla bir işin bulunması güvencesidir; bu amaçla kullanılan kilide sıkça mutex (mutual exclusion lock) denir.9
aynı puan kilidini al
eski puanı oku
artırılmış puanı hesapla
yeni puanı yaz
kilidi bırak
Kilidi alan A'nın işi bitene kadar B aynı kilidi alamaz. A 110'u yazıp kilidi bırakınca B bu kez 110'u okuyup 120'yi yazar. Kilit bütün programın aynı anda yalnızca tek iş yapmasını zorunlu kılmaz; koruduğun ortak durumun erişim kuralını tarif eder. Fakat ilgili bütün erişimler aynı protokole uymalıdır. Bir yol kilidi kullanıp başka bir yol veriye korumasız yazarsa bu düzeni delmiş olursun.9
Kilidi “önce bir Boole değişkenine bak, boşsa dolu yap” diye gelişigüzel yazmak yetmez: iki iş aynı anda boş görebilir. OSTEP bu yüzden doğru kilitlerin donanımın bölünmez işlem desteği ve gerektiğinde işletim sistemi yardımıyla kurulmasını açıklar. Burada kendi kilidimizi icat etmiyoruz. Ayrıca kilit kullanmak bekleme ve maliyet getirir; doğru koruma, adil erişim ve performans ayrı değerlendirme sorularıdır.9
Puan bir Go sürecinin ortak belleğindeyse o sürecin eşgüdüm araçları önemlidir. Ama iki ayrı sunucu süreci aynı Postgres kaydını güncelliyorsa, birinin belleğindeki kilit ötekinin aynı kuralı izlemesini kendiliğinden sağlamaz. Kaynağın paylaşıldığı katmanda çözüm gerekir. Bu yüzden “bir mutex ekledim, bütün sistemde puan artık güvenli” cümlesinin kapsamını sormalısın.
İşlem (transaction), veritabanındaki birden çok adımı tek mantıksal birim halinde ele alan mekanizmadır. PostgreSQL'in öğreticisi, tamamlanmamış değişikliklerin diğer işlemlere nasıl gösterilmediğini ve adımların birlikte onaylanıp geri alınmasını anlatır. Fakat yalnızca “transaction açtım” demek her yarışın her yalıtım düzeyinde çözüldüğü anlamına gelmez. Uygun güncelleme biçimini ve eşzamanlılık davranışını Bölüm 39.3'te inceleyeceğiz. Buradaki bağ: ortak veri varsa erişim sırasının da bir kuralı olmalı.10
14.5 Önizleme: JavaScript olay döngüsü ve Go goroutine'leri
Şimdi iki tanıdık dil adını bu haritaya yerleştirelim. Bu bir JavaScript veya Go dersi değil; o derslerde duyacağın “tek iş parçacığı”, “eşzamansız” ve “hafif iş” sözlerini yanlış kata koymanı önleyen önizleme. Dilin çalışma düzeni ile işletim sisteminin yürütme kaynakları arasında yeni bir katman olabilir.78
JavaScript: tek akışta birden çok olay
“JavaScript tek iş parçacıklıdır” cümlesinin dikkatli hali şudur: aynı JavaScript yürütme birimi, işleri sırayla işler; bir iş bitmeden aynı birimde sıradaki iş onun arasına girmez. MDN bunu işin tamamlanana kadar yürütülmesi (run-to-completion) olarak anlatır. Tarayıcının bütünü ise yalnızca bu yürütme biriminden ibaret değildir; ayrı worker yürütmeleri gibi başka birimler bulunabilir. Dolayısıyla “tek iş parçacığı”ndan “bütün tarayıcı ve bilgisayar tek akışta çalışıyor” sonucu çıkmaz.7
Olay döngüsü (event loop), hazır işlerin sırasını ve yürütülmesini düzenleyen çalışma modeliyle ilgilidir. Eşzamansız (asynchronous) bir istekte “sonuç gelene kadar burada bütün akışı tut” yerine, sonucun geldiğinde devam edecek iş tanımlanabilir. Bir veri isteği beklenirken mevcut iş tamamlanırsa başka hazır işler ele alınabilir. Bu, beklemeyi düzenlemektir; bekleyen hesabın otomatik olarak başka CPU çekirdeğinde yürütülmesi değildir.7
Örneğin hukuk uygulamasında bir soru için veri istenir, sonuç gelene kadar kısa kullanıcı etkileşimleri işlenebilir. Ama aynı JavaScript işinde çok uzun bir sıralama döngüsü çalıştırırsan tıklama işi sırada kalabilir. İşletim sisteminin başka süreçleri zamanlayabilmesi, bu JavaScript biriminin içindeki sıradaki tıklama işini bitmemiş işin arasına soktuğu anlamına gelmez. Burada iki farklı zamanlama katını ayırıyoruz. Olay döngüsünü Bölüm 25.6'da ayrıntılandıracağız.7
Go: goroutine ile işletim sistemi iş parçacığını ayır
Goroutine (goroutine), Go çalışma zamanının yönettiği eşzamanlı yürütme işidir. Effective Go, bunu aynı adres alanındaki diğer goroutine'lerle eşzamanlı çalışan fonksiyon olarak tarif eder. Goroutine'ler birden fazla işletim sistemi iş parçacığı üzerine paylaştırılır. Bu yüzden “bir goroutine açtım” ile “kesin bir yeni OS iş parçacığı açtım” aynı cümle değildir.8
Go programı içindeki goroutine'ler
↓ Go çalışma zamanı paylaştırır
İşletim sisteminin iş parçacıkları
↓ işletim sistemi zamanlar
İşlemcinin yürütme kaynakları
İki katman da kaybolmaz. Go'nun kolay eşzamanlılık sunması, işletim sistemini gereksiz yapmaz; altında yine işletim sistemi kaynakları vardır. Bir goroutine beklerken başkaları ilerleyebilir; uygun donanım ve çalışma düzeninde bazıları paralel de çalışabilir. Ama ortak puana korumasız yazma sorunu sırf işin adı goroutine oldu diye yok olmaz. Aynı adres alanını paylaşmak, ortak durumun eşgüdümünü gerektirir.89
Bu nedenle agent'la konuşurken üç ayrı soru kullanabilirsin: “İşi hangi birimler arasında böldün?”, “Bunlar hangi veriyi paylaşıyor?”, “Beklerken veya güncellerken doğruluğu nasıl koruyorsun?” “Daha fazla thread ekledim” ya da “async yaptım” bu soruların tamamının cevabı değildir. Go'nun sözdizimini, goroutine ve kanalları Bölüm 36.4'e bırakıyoruz.
Bölüm 6'daki program sayacı artık “bir işin nerede kaldığı” bilgisi oldu. Bölüm 10.4'ün yığını her iş parçacığının çağrı geçmişini tuttu. Bölüm 11.5'teki kuyruk fikri hazır işlerin düzenini düşünmemizi sağladı; her gerçek zamanlayıcının tek basit FIFO kuyruk kullandığı iddia edilmiyor. Bölüm 13'ün kaynak yöneticisi ise bu yürütmeleri paylaştırdı. Bütün parçalar aynı binanın komşu odaları.
Bir sonraki bölüm “bu işlerin belleği birbirine neden karışmıyor?” sorusunu açacak. Bu bölümde süreç ve iş parçacığını ayırdık; Bölüm 15 şimdi süreçlerin ayrı adres alanlarının nasıl kurulabildiğini gösterebilir.
Bu bölümde neler kuruldu
Program tarif, süreç o tarifin çalışan örneği ve durumudur. Zamanlayıcı hazır yürütmeyi seçer; bağlam değişimi nerede kalındığını korur. Hazır olmak ile bir olayı beklemek farklıdır. İş parçacıkları aynı süreçte adres alanını paylaşırken ayrı yürütme durumları ve yığınlar taşır. Eşzamanlılık iç içe ilerleme, paralellik aynı anda fiziksel yürütmedir. Ortak verinin kritik adımları korunmazsa güncelleme kaybolabilir; kilit, doğru kapsamda karşılıklı dışlama sağlar. JavaScript olay döngüsü ile Go goroutine’leri bu sorunları başka çalışma modellerinde yeniden karşımıza çıkarır.
Terimler sözlükte, Bölüm 14 başlığı altında.
Birincil kaynak: OSTEP, The Abstraction: The Process, özellikle The Abstraction: A Process ve Process States. Derinleşme: Concurrency: An Introduction, Why Use Threads? ve Why It Gets Worse: Shared Data; Locks — The Basic Idea.
Takıldığın yeri yapay zekâ öğretmenine sor. Ondan iki puan güncellemesinin altı adımını karışık sırada vermesini iste; hangi sıranın 110, hangisinin 120 bıraktığını çizerek bul. Sonra kilidin hangi adımların tamamını kapsaması gerektiğini sen işaretle.
Bölüm sonu soruları
Önce kitaba bakmadan cevapla. Geri bildirimdeki bağlantıyla düşünceni denetle.
Örnek cevabı göster
İki iş kilitsiz kısımda aynı eski değeri okuyabilir. Sonra sırayla kilit alıp aynı hesaplanmış yeni değeri yazabilirler. Okuma, hesaplama ve yazma arasındaki kritik ilişki korunmalıdır; yalnızca yazmaların sıraya girmesi yeterli olmayabilir. İlgili erişimler aynı eşgüdüm protokolünü izlemelidir. Bkz. 14.4.
Örnek cevabı göster
Aynı JavaScript yürütme birimi işleri sırayla tamamlar ama olay döngüsüyle farklı işlerin ilerlemesi yönetilebilir; tarayıcıda ayrı worker birimleri de bulunabilir. Go goroutine’leri eşzamanlı işleri OS iş parçacıklarına paylaştırır; ortak veri üzerindeki korumasız erişimi kendiliğinden doğru yapmaz. Eşzamanlılık ile paralellik ve paylaşılan verinin doğruluğu ayrı sorulardır. Bkz. 14.5.
Kaynaklar
- Arpaci-Dusseau & Arpaci-Dusseau, OSTEP — The Abstraction: The Process: Süreç durumu, kod/veri/yazmaçlar, süreç oluşturma ve ready/running/blocked ayrımı.
- OSTEP — Mechanism: Limited Direct Execution: Switching Between Processes; Saving and Restoring Context. Kesme, denetimin geri alınması ve bağlam değişimi.
- OSTEP — Scheduling: Introduction: A New Metric: Response Time; Round Robin; dilim uzunluğu ile geçiş maliyeti arasındaki pazarlık. Bölümdeki milisaniye sayıları ölçüm değil, ayrıca hesaplanmış varsayımlı örneklerdir.
- OSTEP — Concurrency: An Introduction: Ayrı program sayacı/yazmaç/yığın, ortak adres alanı; Why Use Threads; Shared Data; kesmeyle bölünen sayaç güncellemesi.
- Apple — View information about Mac processes in Activity Monitor: Süreçlerin CPU/bellek tüketimini görüntüleme; görünmeyen arka plan süreçleri ve sütun seçimi.
- Microsoft Learn — Find the process ID: PID ve Task Manager üzerinden süreç kimliğini görüntüleme.
- MDN — JavaScript execution model: Agent execution model; Job queue and event loop; Run-to-completion; worker ayrımı. MDN’deki agent burada yapay zekâ agent’ı değildir.
- The Go Authors — Effective Go, Concurrency — Goroutines: Aynı adres alanındaki eşzamanlı fonksiyonlar ve goroutine’lerin işletim sistemi iş parçacıklarına paylaştırılması.
- OSTEP — Locks: Locks: The Basic Idea; Pthread Locks; Building A Lock; Evaluating Locks. Karşılıklı dışlama ve gerçekleme desteği.
- PostgreSQL — Tutorial, Transactions: Birlikte onaylanan/geri alınan adımlar ve tamamlanmamış değişikliklerin diğer işlemlerle ilişkisi; ayrıntılı yalıtım düzeyleri sonraki bölümün konusu.