C Hacks'ten C++20'ye: Eşzamanlı İşlemlerin Evrimi

Son Güncelleme: 12/27/2025
  • Eş zamanlı işlemler (coroutines), yerel durumu koruyarak ve askıya alma noktalarında yürütmeye devam ederek alt programları genelleştirir; bu da durum makinelerinin, üreteçlerin ve işbirlikçi eşzamanlılığın doğal bir şekilde ifade edilmesini sağlar.
  • C uygulamaları, manuel yığın manipülasyonundan ve POSIX bağlam API'lerinden, makro tabanlı yaklaşımlara ve kullanıcı düzeyinde bağlam değiştirme üzerine kurulu taşınabilir eşzamanlı işlem kütüphanelerine doğru evrimleşmiştir.
  • C++20, promise'lerle birlikte yığınsız bir eşzamanlı işlem modelini standartlaştırıyor. co_await, co_yield ve eşzamanlı işlem çerçeveleri, kütüphanelerin üst düzey asenkron ve üretici soyutlamalarını tanımlamasına olanak tanır.
  • Standartlaştırılmış model, awaiter'lar ve özel promise türleriyle birleşerek, öngörülebilir performans ve kontrolü korurken, kütüphaneler genelinde eşzamanlı işlem kullanımını birleştirir.

C'den C++'a eşzamanlı işlemlerin evrimi

Eş zamanlı işlemler (coroutines), klasik fonksiyonlar ve tam teşekküllü iş parçacıkları (threads) arasında büyüleyici bir orta noktada yer alır. Düşük seviyeli C kodlarından standartlaştırılmış C++20 dil desteğine uzanan hikayeleri, modern sistem programlamasındaki en ilginç gelişmelerden biridir. Engellenmeyen G/Ç işlemlerini yönetmek için geri çağırma fonksiyonları, durum makineleri ve iş parçacığı senkronizasyonu arasında denge kurmaya çalıştıysanız, eş zamanlı işlemlerin (coroutines) çözmek için tasarlandığı türden bir acıyla zaten karşılaşmışsınız demektir.

Bu makalede, eşzamanlı işlevlerin (coroutines) el yapımı C kodlarından ve POSIX bağlam API'lerinden yüksek seviyeli, yığınsız C++20 eşzamanlı işlev modeline nasıl evrimleştiğini ele alacağız. Bir coroutine'in gerçekte ne olduğunu, jeneratörlerden, iş parçacıklarından ve fiberlerden nasıl farklı olduğunu, "yığınlı" ve "yığınsız" kavramlarının ne anlama geldiğini ve C++20 mekanizmasının (promise nesneleri, coroutine işleyicileri, co_await, co_yield, co_return) aslında arka planda nasıl davrandığını gösteriyor.

Eşzamanlı işlem (coroutine) tam olarak nedir?

csharp'ın derin özellikleri
İlgili makale:
C#'ın özellikleri, ekosistemi ve araçları hakkında detaylı bilgi.

Eş zamanlı işlem (coroutine) için evrensel olarak kabul görmüş tek bir resmi tanım yoktur. Ancak literatür, eşzamanlı işlemleri sıradan alt işlemlerden ayıran iki temel özellik üzerinde hemfikirdir:

  • Yerel yönetimler askıya alma süreçlerine rağmen varlıklarını sürdürüyor: Bir eşzamanlı işlem içindeki yerel veriler, etkinleştirmeler arasında korunur; bu nedenle her eşzamanlı işlem örneği, belleğe sahip bir nesne gibi davranır.
  • İşlem duraklatılabilir ve daha sonra aynı noktadan devam ettirilebilir: Kontrol bir coroutine'den ayrıldığında, normal bir fonksiyonda olduğu gibi her zaman en baştan başlamak yerine, tam olarak askıya alındığı noktadan tekrar girilebilir.

Alt programların aksine, tek seferlik giriş ve çıkış noktalarına sahip olmayan eşzamanlı işlemler (coroutines), ömürleri boyunca birden fazla giriş ve çıkış noktasını destekler. Bu da onları üreticileri, tüketicileri, durum makinelerini, işbirlikçi zamanlayıcıları ve eşzamansız iş akışlarını doğrusal ve okunabilir bir üslupla ifade etmek için güçlü kılıyor.

Eş zamanlı işlem tasarımının temel boyutları

Gerçek dünyadaki eşzamanlı işlem sistemleri, davranışlarını ve ifade güçlerini belirleyen üç önemli eksen boyunca farklılık gösterir: Kontrol aktarım modeli, eşzamanlı işlemlerin birinci sınıf değerler olup olmadığı ve yığınlı mı yoksa yığınsız mı oldukları gibi konuları içerir.

İlk olarak, kontrol aktarım mekanizması asimetrik ve simetrik eşzamanlı işlemleri birbirinden ayırır. Asimetrik bir tasarımda, aktif eşzamanlı işlem yalnızca doğrudan çağırana geri dönebilir (kavramsal olarak benzer bir işlem kullanarak). yield), ve arayan kişi daha sonra (şuna benzer bir işlemle) görüşmeye devam eder: resumeSimetrik tasarımlarda, bir eşzamanlı işlem, her zaman onu çağıran kişiye geri dönmek yerine, kontrolü açıkça başka bir eşzamanlı işleme aktarabilir.

İkinci olarak, bazı diller eşzamanlı işlem örneklerini, serbestçe saklayabileceğiniz, aktarabileceğiniz ve manipüle edebileceğiniz birinci sınıf nesneler olarak ele alır. Diğerleri ise eşzamanlı işlevleri yalnızca sınırlı etkileşim yollarına sahip sözdizimsel yapılar olarak sunar. Birinci sınıf destek, esnekliği ve birleştirilebilirliği önemli ölçüde artırır.

Üçüncüsü, eşzamanlı işlemler yığınlı veya yığınsız olabilir. Yığın dolu bir eşzamanlı işlem, iç içe geçmiş bir çağrı yığınının derinliklerinde askıya alınabilir; devam ettiğinde, o yığındaki her çerçeve kaldığı yerden devam eder. Yığınsız eşzamanlı işlemler yalnızca eşzamanlı işlem fonksiyonunun kendi seviyesinde askıya alınır: normal yardımcı fonksiyonlar, kendileri eşzamanlı işlem olmadıkça veya özel olarak işaretlenmedikçe işlem döndüremezler.

"Tam eş zamanlı işlem" terimi, en anlamlı kombinasyon için önerilmiştir: simetrik veya asimetrik, bir yığın halinde bulunan, birinci sınıf bir eş zamanlı işlem. Tek seferlik devamlılıkları veya sınırlı devamlılıkları ifade edebilecek kadar güçlüdür. Simetrik ve asimetrik stiller eşdeğer ifade gücüne sahip olsa da, asimetrik model çoğu programcıya daha "rutin benzeri" ve tanıdık gelir.

Alt programlar, eş zamanlı işlemler, üreteçler ve iş parçacıkları

Alt programlar, kontrol akışı ve durum davranışı ciddi şekilde kısıtlanmış, eş zamanlı işlemlerin özel bir durumu olarak düşünülebilir. Normal bir fonksiyon her zaman ilk komutuyla başlar, bir kez çıkar ve sonrasında yerel durumunu atar. Buna karşılık, bir eş zamanlı işlem (coroutine), kontrolü diğer eş zamanlı işlemlere aktarabilir, daha sonra yield noktasında devam ettirilebilir ve bu aktarımlar boyunca durumunu koruyabilir. Aynı fonksiyonun birden fazla eş zamanlı işlem örneği, her biri kendi korunmuş yerel verilerine sahip olarak birlikte var olabilir.

Jeneratörler, bazen "yarı eş zamanlı işlemler" olarak da adlandırılan eş zamanlı işlemlerin önemli bir alt kümesini oluşturur. Eş zamanlı işlemler gibi, üreteçler de yürütmeyi birden fazla kez askıya alabilir ve daha sonra devam edebilir, ancak her zaman doğrudan çağıranlarına geri dönerler ve yürütmeyi keyfi bir üçüncü eş zamanlı işleme yönlendirmenin bir yolu yoktur. Bu kısıtlama kasıtlıdır: üreteçler, her birinin bir döngü oluşturduğu yineleyicileri ve tembel dizileri uygulamak için optimize edilmiştir. yield Basitçe "beni yineleyen herkes için bir değer üret" anlamına gelir.

Aslında, bir jeneratör sisteminin üzerine bir dağıtıcı katmanlayarak genel eşzamanlı işlemleri taklit edebilirsiniz. Örneğin, jeneratörlerden token alan ve bir sonraki hangi jeneratörü etkinleştireceğine karar veren üst düzey bir trampolin oluşturarak. Bu teknik, tarihsel olarak yalnızca jeneratörlere sahip olan ancak yerleşik eşzamanlı işlem temel öğelerine sahip olmayan Python'ın ilk sürümleri gibi dillerde kullanılmıştır.

Eş zamanlı işlemler (coroutines) sıklıkla iş parçacıklarına (threads) benzetilir, ancak temelde önleyici paralellikten ziyade işbirlikçi zamanlama ile ilgilidirler. Eş zamanlı işlemler (coroutines), genel anlamı değiştirmeden görevlerin iç içe geçmesi anlamında eşzamanlılık sağlarlar, ancak kendi başlarına birden fazla çekirdekte aynı anda çalışmazlar. Bir eş zamanlı işlem yalnızca açıkça belirtilen askıya alma noktalarında kontrolü bırakır, bu nedenle bu noktalar arasındaki kod, diğer eş zamanlı işlemlerden kesintiye uğramadan çalışır.

Bu işbirlikçi model, çoklu iş parçacıklarında sıkça karşılaşılan birçok senkronizasyon sorununu ortadan kaldırır: Çünkü belirli bir zamanlayıcıda aynı anda yalnızca bir eş zamanlı işlem çalışır, bu nedenle sıradan paylaşılan durum için genellikle karşılıklı dışlama kilitlerine veya atomik işlemlere ihtiyaç duymazsınız. Öte yandan, eş zamanlı işlemler tek başlarına, onları iş parçacıklarıyla veya çok iş parçacıklı bir yürütücüyle birleştirmediğiniz sürece birden fazla CPU çekirdeğini kullanmazlar.

Klasik eşzamanlı işlem örneği: üretici-tüketici

Simetrik eşzamanlı işlemlerin ders kitaplarında yer alabilecek en iyi örneği, paylaşımlı kuyruğa sahip üretici-tüketici modelidir. Bir eş zamanlı işlem (coroutine) öğeler üretir ve bunları dolana kadar bir kuyruğa ekler, ardından tüketiciye devreder; tüketici, kuyruk boşalana kadar öğeleri çıkarır, ardından üreticiye geri devreder. Her iki taraf da işbirliği içinde kontrolü devrettikçe, işlem ileri geri gidip gelir.

Bu tür bir uygulamada, programcının bakış açısından üretici ve tüketici "paralel" olarak çalışıyor gibi görünür. Aslında tek bir yürütme iş parçacığı içinde ileri geri atlama yapıyorlar. İşletim sistemi düzeyinde iş parçacıklarına veya bağlam değiştirme işlemlerine gerek yok: yield işlemi, aktif yığın çerçevesini yeniden düzenleyen düşük seviyeli bir atlama olabilir.

Bu örnek sıklıkla çoklu iş parçacığı (multithreading) kavramını tanıtmak için kullanılır, ancak mantığı ifade etmek için yalnızca eşzamanlı işlemlerin (coroutines) yeterli olduğunu belirtmek önemlidir. ve bunların iş parçacıklarıyla değiştirilmesinin, gerçek zamanlı garantilere veya minimum çalışma zamanı yüküne önem veren ortamlarda gereksiz, hatta zararlı olabileceği düşünülmektedir.

Eşzamanlı işlemlerin önemi: durum makineleri, aktörler ve asenkron iş akışları

Çünkü eşzamanlı işlemler, yürütme noktalarını ve yerel değişkenlerini çıktılar arasında korurlar, Karmaşık durum makinelerini, uzun switch ifadeleri, bayraklar veya açık program sayaçları kullanmadan çok doğal bir şekilde uygulama imkanı sağlarlar. Mevcut askıya alma noktası, kelimenin tam anlamıyla mevcut durumu temsil eder.

Eş zamanlı işlemler (coroutines) aktör tarzı eşzamanlılık modellerine de oldukça uygundur. Örneğin birçok oyun motorunda kullanılanlar gibi. Her aktör, periyodik olarak kontrolü merkezi bir zamanlayıcıya geri veren bir eş zamanlı işlem (coroutine) olarak uygulanabilir; bu zamanlayıcı, tek bir iş parçacığında bir aktörü diğerinin ardından çalıştırır. Bu işbirlikçi çoklu görevlendirme, duyarlı davranış sağlarken çoğu kilitleme ihtiyacını ortadan kaldırır.

Eş zamanlı işlemler üzerine kurulu jeneratörler, veri akışlarıyla ve veri yapısı gezintileriyle çalışmak için idealdir. Özellikle tembel, isteğe bağlı değer üretimi istediğinizde. Değerleri bir tüketiciye itmek yerine, bir üretici, tüketicinin basit bir döngü kullanarak değerleri tek tek çekmesine olanak tanır.

Eş zamanlı işlemler (coroutines) ayrıca işlem hatları (pipelines) ve iletişim kuran sıralı süreçler (CSP) gibi iletişim modellerinde de öne çıkar. Burada her aşama, girdi veya çıktı beklediğinde işlemden çıkan bir eş zamanlı işlemdir. Zamanlayıcı daha sonra iletişim kanalları hazır olduğunda eş zamanlı işlemleri devam ettirir ve geri çağırma ağırlıklı olay döngülerine zarif bir alternatif sunar.

Son olarak, birçok sayısal kütüphane bazen "ters iletişim" olarak adlandırılan bir stil kullanır. Burada, bir çözümleyici, kullanıcının bir fonksiyon değerlendirmesi sağlaması gerektiğinde kendini askıya alır ve kullanıcı yanıt verdiğinde tekrar çalışmaya başlar. Eş zamanlı işlemler (coroutines), bu ileri geri kontrol akışını ifade etmenin doğrudan ve anlaşılır bir yolunu sunar.

Düşük seviyeli C uygulamalarından taşınabilir kütüphanelere

Bir uygulama sınıfı, ikinci çağrı yığınını manuel olarak elde eder ve ardından bunu kullanır. setjmp/longjmp Eş zamanlı işlemler arasında geçiş yapmak için. Platforma özgü satır içi derleme, her bir eşzamanlı işlem için yeni bir yığın oluşturabilir; POSIX sistemlerinde, sinyaller şunlarla birleştirilir: sigaltstack Bu, saf C'de alternatif bir yığın üzerinde yürütmeyi başlatmak için kullanılabilir. Her bir eşzamanlı işlem kendi yığınına sahip olduktan sonra, setjmp CPU durumunu ve yığın işaretçisini kaydeder ve longjmp Bu, eşzamanlı işlemin devam etmesi için onları eski haline getirir.

Geçmişte bazı POSIX ve UNIX uyumlu C kütüphaneleri, aşağıdaki gibi yardımcı fonksiyonlar sunmuştur: getcontext, setcontext, makecontext hem de swapcontext, Bu yapılar, kullanıcı düzeyindeki bağlamlar arasında geçiş yapma fikrini doğrudan kapsar. Her ne kadar POSIX.1-2008'de kullanım dışı olarak işaretlenmiş olsalar da, çeşitli eşzamanlı işlem kütüphanelerinin omurgasını oluşturmuş ve daha sonraki tasarımlara ilham kaynağı olmuşlardır.

Minimal eş zamanlı uygulama uygulamaları atlanır setjmp/longjmp ve bağlam API'lerinin tamamı, Bunun yerine, yalnızca program sayacını ve yığın işaretçisini değiştiren ve diğer kayıtları geçersiz kılan elle yazılmış assembly kodunu tercih etmek. Bu, bazı ABI'lerde önemli ölçüde daha hızlı olabilir çünkü yalnızca gerekli olanı kaydeder ve fazlasını kaydetmez. setjmp Daha büyük bir kayıt kümesini ihtiyatlı bir şekilde saklamak zorundadır.

Tüm bu karmaşıklığı uygulama kodundan gizlemek için, yıllar içinde eşzamanlı işlem geçişini temiz API'ler halinde paketleyen birçok C kütüphanesi ortaya çıktı. Russ Cox'unki gibi libtask ve çeşitli diğerleri (libpcl, coro, lthread, libcoro, libaco, libco ve daha fazlası). Bu kütüphaneler tipik olarak, çağıranın altta yatan derleme işlemlerine kafa yormasına gerek kalmadan devam ettirilebilen ve geri döndürülebilen hafif görevler veya fiberler gibi soyutlamalar sağlar.

Makrolar kullanarak C dilinde yaklaşık eşzamanlı işlemler (coroutines) oluşturma

Ayrı yığınların veya bağlam değiştirme API'lerinin mevcut olmadığı veya istenmediği durumlarda, geliştiriciler eş zamanlı işlemleri saf C dilinde makrolar ve switch ifadeleriyle de yaklaşık olarak oluşturmuşlardır. Simon Tatham tarafından meşhur bir şekilde belgelenmiş ve klasik "Duff'ın cihazı" hilesiyle ilişkili bir teknik.

Temel fikir, eşzamanlı işlemin durumunu, bir program sayacı olarak kodlayarak uygulamaktır. switch hem de case etiketler, her biri yieldBu makro, geçerli etiketi statik bir değişkene kaydeden ve ardından çağırana geri dönen bir koda genişler. Bir sonraki çağrıda, fonksiyon baştan başlamak yerine o etikete geri döner.

Protothreads gibi kütüphaneler, bu modeli temel alarak kısıtlı gömülü ortamlara uygun, son derece hafif ve yığın gerektirmeyen eşzamanlı işlemler sunar. Ancak bu yaklaşımın ciddi sınırlamaları vardır: yerel değişkenler, statik veya harici yapılarda saklanmadıkça, işlem tekrarları arasında doğal olarak kalıcı olmazlar; iç içe fonksiyon çağrılarından kolayca askıya alma işlemi yapamazsınız ve genellikle yalnızca tek bir giriş noktanız olur.

Hatta bu makro tabanlı hilekarlığı savunanlar bile, üretimde kullanılan en çirkin C kodlarından bazıları olarak tanımlıyor. Eleştirmenler, ortaya çıkan kontrol akışının anlaşılmasının ve zaman içinde sürdürülmesinin zor olabileceğine dikkat çekiyor. Bununla birlikte, ek yığınların veya bağlayıcı hilelerinin mümkün olmadığı sistemlerde kullanışlı bir uzlaşma olmaya devam ediyor.

Basamak taşı: lifler, iplikler ve ilgili soyutlamalar

Yerleşik eşzamanlı işlem (coroutine) özelliğinin bulunmadığı ana akım ortamlarda, iş parçacıkları (ve daha az ölçüde fiberler) eşzamanlılık için varsayılan yapı taşı haline gelmiştir. İşbirlikçi davranışın yeterli olacağı durumlarda bile. İş parçacıkları genellikle iyi desteklenir ve iyi belgelenir, ancak çoğu eşzamanlı işlem kullanım durumunun gerçekten ihtiyaç duyduğundan daha geniş ve karmaşık bir sorunu çözerler.

Mümkün olan yerlerde fiberler, işbirliğine dayalı olarak planlandıkları ve işletim sisteminin müdahalesi olmadan değiştirilebildikleri için kullanıcı düzeyindeki eşzamanlı işlemlere daha yakın bir eşleşme sunarlar. Bu durum, fiberleri eşzamanlı işlem tarzı API'leri uygulamak için doğal bir zemin haline getiriyor. Bununla birlikte, fiberler için sistem desteği, iş parçacıklarına kıyasla yetersizdir ve taşınabilirlik olumsuz etkilenir.

İş parçacıkları ve eş zamanlı işlemler arasındaki önemli farklardan biri zamanlama davranışıdır. İş parçacıkları genellikle rastgele noktalarda önceliklendirilerek durdurulur; bu da programcıları her yerde yarış koşulları ve senkronizasyon hakkında düşünmeye zorlar. Buna karşılık, eş zamanlı işlemler (coroutines) yalnızca açık durdurma noktalarında kontrolü değiştirir; bu da genellikle kilitler veya atomik işlemler olmadan daha basit kod yazmanıza olanak tanır.

Diller ve çalışma ortamları, mevcut altyapı üzerinde eşzamanlı işlemleri taklit etmek için birçok yol denemiştir. (Bazı Java eşzamanlı işlem çerçevelerinde olduğu gibi) bayt kodunu yeniden yazmaktan, eşzamanlı işlem benzeri yapıları yineleyicilere eşlemeye kadar (C#'ın yaptığı gibi) yield önce async/await) veya bunları yeşil ipliklerin, devamlılıkların veya liflerin üzerine inşa ederek.

Programlama dilleri genelinde eş zamanlı işlemler

On yıllar boyunca birçok dil, her biri kendine özgü özelliklere ve avantaj/dezavantajlara sahip, eşzamanlı işlem benzeri yapılarla denemeler yapmıştır. Bu ekosistemi anlamak, C++'ın evrimini bağlamına oturtmamıza yardımcı olur.

Bazı programlama dilleri, birinci sınıf, yığınlanabilir eşzamanlı işlevleri doğrudan çalışma zamanında ve standart kütüphanede sunar. Örneğin Lua, 5.0 sürümünden beri standart yapısı aracılığıyla asimetrik, yığınlı eşzamanlı işlemleri desteklemektedir. coroutine API, oluşturma, devam ettirme ve geri döndürme gibi temel işlevlere sahiptir. Modula-2, geçmişte prosedürler aracılığıyla eş zamanlı işlem desteğini içermekteydi. NEWPROCESS hem de TRANSFER Ayrı yığınlar oluşturan ve bağlamlar arasında geçiş yapan yapılar.

Diğer ekosistemler, devamlılıklar veya yeşil iplikler gibi mevcut temel işlevlerin üzerine eşzamanlı işlevler inşa ettiler. Racket (ve genel olarak Scheme lehçeleri), devamlılıkları birinci sınıf değerler olarak ortaya koydukları için eş zamanlı işlemleri neredeyse önemsiz bir şekilde uygulayabilirler. Yürütme yığınlarının manipüle edilebilir nesneler olduğu Smalltalk sistemleri de benzer şekilde ek VM desteği olmadan eş zamanlı işlem soyutlamalarını barındırabilir. OCaml'de, işbirlikçi eşzamanlılık, iş parçacıklarını tek bir işletim sistemi iş parçacığında öncelikli olarak planlayan modüller aracılığıyla sağlanırken, daha yeni sürümler yeşil iş parçacığı stili desteği ekler.

Asenkron programlamaya odaklanan diller, tam teşekküllü eşzamanlı işlevleri (coroutines) tanıtmadan önce genellikle jeneratörlerle (generators) başlamıştır. C# başlangıçta jeneratörleri şu şekilde ekledi: yield ve yineleyici deseni, daha sonra evrimleşerek şu hale geldi: async/await Asenkron işlemleri eşzamanlı işlemler (coroutines) olarak modellemek. JavaScript de benzer bir yol izledi: ES2015, eşzamanlı işlemlerin özel bir durumu olarak jeneratörleri tanıttı ve sonraki sürümler bunları ekledi. async/await Vaatler ve jeneratörler üzerine inşa edilmiş.

JVM dünyasında, Java'nın kendisi yerleşik eşzamanlı işlemler (coroutines) sunmaz, ancak etrafındaki araçlar ve diller bu açığı kapatır. Bazı kütüphaneler, eşzamanlı işlem davranışını simüle etmek için bayt kodunu değiştirirken, diğerleri platforma özgü mekanizmalara erişmek için JNI kullanır ve bazıları daha yüksek bir maliyetle eşzamanlı işlem semantiğini taklit etmek için iş parçacıklarına güvenir. Öte yandan Kotlin, eşzamanlı işlemleri birinci taraf bir kütüphane özelliği olarak sunar ve Java koduyla birlikte çalışabilir (ancak Java doğal olarak "askıya alamaz" ve bunun yerine engellemeli veya gelecekleri kullanmalıdır).

Betikleme ve dinamik diller çeşitli yaklaşımlar benimsemiştir. Python, geliştirilmiş jeneratörlerle (PEP 342) başladı, bunları alt jeneratör delegasyonuyla (PEP 380) genişletti ve sonunda açık yerel eşzamanlı işlevleri tanıttı. async/await (PEP 492), daha sonra bu anahtar kelimeleri Python 3.7'de saklı tuttu. Ruby, fiberler aracılığıyla eş zamanlı işlem benzeri davranışlar uygular; Raku ve Tcl, yerel eş zamanlı işlem yapıları sunar; PHP 8.1, eş zamanlı olmayan G/Ç için eş zamanlı işlem tabanlı kütüphaneleri desteklemek üzere fiberler ekledi.

Sistem odaklı diller de kendi yorumlarıyla eşzamanlı işlem benzeri modelleri keşfederler. Go, dinamik boyutlu yığınlara sahip hafif, çoklu işlem süreçleri olan goroutine'leri kullanır. Goroutine'ler, kesin anlamda eşzamanlı işlemler olmasa da (yeşil iş parçacıklarına daha yakındırlar ve yerel veriler eşzamanlı işlem anlamında birden fazla 'çağrıdan' sağ çıkamaz), çalışma zamanı zamanlayıcısı tarafından yönetilen kullanıcı düzeyindeki görevlerle benzer bir zihinsel alanı işgal ederler. D, eşzamanlı işlemleri şu şekilde sunar: Fiber Standart kütüphanesinde yer alırlar ve bazı çerçeveler bunları kullanışlı jeneratör tarzı arayüzlere dönüştürür.

C++'a giriş: standarttan önce kütüphaneler.

C++'ın eşzamanlı işlemleri standartlaştırmasından önce, ekosistem eşzamanlı işlem semantiğini dile getirmek için üçüncü taraf kütüphanelere güveniyordu. Assembler bağlam değiştirme, platform API'leri ve akıllı şablon metaprogramlamanın bir karışımını kullanarak.

Boost.Context, birden fazla mimari ve işletim sistemi arasında yürütme bağlamlarını değiştirmek için düşük seviyeli bir temel olarak ortaya çıktı. Kullanıcı alanı yığınlarını manipüle etmek için taşınabilir bir yol sağladı. Bunun da ötesinde, Boost.Coroutine ve daha sonra Boost.Coroutine2, daha üst düzey eşzamanlı işlem soyutlamaları sunarak, hem simetrik hem de asimetrik formlara yönelik destekten, çağdaş C++ kalıplarıyla daha iyi uyum sağlayan daha modern bir asimetrik arayüze geçiş yaptı.

Diğer projeler ise önişlemci tabanlı yığınsız eşzamanlı işlemler gibi farklı açılardan incelemeler yaptı. await/yield anlambilim, Platform fiberlerini saran tek başlıklı kütüphaneler veya özellikle eşzamanlı olmayan G/Ç geri çağırmalarını eşzamanlı işlem benzeri sıralı kodun arkasına gizlemeye odaklanan çerçeveler (Mordor veya Oat++ eşzamanlı işlemleri gibi).

Bu ekosistemler, C++ geliştiricilerinin eşzamanlı işlem benzeri ifade gücüne duydukları özlemi ortaya koydu. Ancak bu durum, geçici çözümlerin dezavantajlarını da ortaya çıkardı: tutarsız sözdizimi, zorlu taşınabilirlik sorunları, kullanışsız hata ayıklama ve araçlar ile standart kütüphanenin geri kalanıyla karmaşık entegrasyon.

C++20 eşzamanlı işlemler: standartlaştırılmış, yığınsız bir model

C++20 nihayet eşzamanlı işlemleri (coroutines) birinci sınıf bir özellik olarak dile dahil etti. Ancak kasıtlı olarak minimal ve düşük seviyeli bir tasarımla. C++20, standart kütüphaneye belirli bir yüksek seviyeli soyutlama (örneğin "görev", "üretici" veya "gelecek") eklemek yerine, kütüphanelerin kendi eşzamanlı işlem dostu türlerini tanımlamalarına olanak tanıyan yapı taşlarını standartlaştırdı.

Bir fonksiyon, gövdesi eşzamanlı işlem (coroutine) özgü yapıların herhangi birini içeriyorsa eşzamanlı işlem haline gelir: the co_await Operatörün, bir olay veya değer hazır olana kadar beklemesi, co_yield Bir değer üretmek ve ardından durdurmak için kullanılan ifade (üreticilerde olduğu gibi) veya co_return Eş zamanlı işlemi tamamlamak için kullanılan ifade, isteğe bağlı olarak bir sonuç da içerebilir.

Derleyici bu yapıların herhangi birini tespit ettiğinde, fonksiyonu kalıcı durumu yığın bellekte ayrılmış bir "eş zamanlı işlem çerçevesi"nde depolanan bir durum makinesine dönüştürür. Optimizasyon, çerçevenin ömrünün kesinlikle çağıranın içinde iç içe geçtiğini ve çağıranın yığın çerçevesine yerleştirilebileceğini kanıtlamadığı sürece, bu çerçeve vaat nesnesini, argümanların kopyalarını, askıya alma noktaları boyunca yaşayan yerel değişkenleri ve yürütmeye devam etmek için kayıt tutma meta verilerini içerir.

En önemlisi, C++20 eşzamanlı işlemleri yığınsızdır: Bir eşzamanlı işlem yalnızca açıkça belirtilen askıya alma noktalarında askıya alınabilir (co_await or co_yield(ve bu fonksiyonlar da eş zamanlı çalışan fonksiyonlar olmadığı veya eş zamanlı çalışan mekanizmaya katılmadığı sürece, keyfi iç içe çağrılardan şeffaf bir şekilde çıktı veremez.) Bu, tam yığınlı tasarımlara kıyasla bazı ifade gücünden ödün vererek uygulamayı daha basit ve daha tahmin edilebilir hale getirir.

C++20 eşzamanlı işlemlerinin kısıtlamaları ve yaşam döngüsü

C++20'deki her fonksiyonun eş zamanlı işlem (coroutine) olması mümkün değildir; standart, modelin mantıklı kalması için çeşitli kısıtlamalar getirir. Eş zamanlı işlemler (coroutines) olamaz. constexpr or consteval Fonksiyonlar, yapıcı, yıkıcı veya benzeri fonksiyonlar olamazlar. main İşlevsel olarak, C tarzı değişken sayıda argüman veya düz metin gibi yer tutucu dönüş tipleri kullanamayabilirler. auto Ek bir özellik belirtilmeden.

Bir eşzamanlı işlem ilk çağrıldığında, hemen normal bir fonksiyon gövdesi gibi davranmaz. Bunun yerine, derleyici tarafından oluşturulan prolog, eşzamanlı işlem çerçevesini (genellikle şu yolla) tahsis eder: operator new), fonksiyon parametrelerini (belirtildiği gibi değer veya referans yoluyla) o çerçeveye kopyalar, promise nesnesini oluşturur ve ardından çağırır. promise.get_return_object()Bu işlem genellikle çağırana döndürülen bir tanıtıcı veya sarmalayıcı nesne üretir.

Vaat nesnesi, kullanıcı tanımlı bir türdür ve şu yolla keşfedilir: std::coroutine_traits Eş zamanlı işlemin dönüş türüne ve parametre listesine bağlı olarak, ve sonuçların, istisnaların ve askıya alma politikalarının nasıl işleyeceğini belirler. Derleyici bir çıkarım yapar. Promise türünü belirler ve ardından şu gibi yöntemleri çağırır: initial_suspend(), final_suspend(), return_value() or return_void(), ve unhandled_exception() Eş zamanlı işlem yaşam döngüsünün uygun aşamalarında.

Yürütmenin başlangıcında, eşzamanlı işlem çağrı yapar. promise.initial_suspend() hem de co_awaitGeri dönen her ne olursa olsun, Bu, kütüphane yazarlarının eşzamanlı işlem türlerinin "aceleci" (hemen çalışmaya başlar) veya "tembel" (açıkça devam ettirilene kadar çağırana geri döner) olup olmadığına karar vermelerini sağlar. Eşzamanlı işlem sonunda şu şekilde sona erer: co_return veya ele alınmayan bir istisna durumunda, bunu çağırır. promise.final_suspend()Bu durum, kütüphaneye devam programlarını planlamak veya temizlik yapmak için son bir şans tanıyor.

Eş zamanlı işlem çerçevesi, tamamlandıktan sonra veya tanıtıcısı üzerinde açık bir yok etme işlemiyle yok edildiğinde; Çalışma zamanı, vaat edilen nesneyi, parametrelerin kopyalarını ve kalan tüm canlı yerel değişkenleri yok eder, ardından belleği serbest bırakır. operator delete (veya sağlanmışsa, söze özgü bir tahsis edici ile). Tahsis başarısız olursa ve söz tanımlanmışsa get_return_object_on_allocation_failure()Eş zamanlı işlem, hata fırlatmadan düzgün bir şekilde başarısızlığı bildirebilir. std::bad_alloc.

co_await, beklenebilir ve bekleyenler

MKS co_await Operatör, C++20'nin eşzamanlı işlem sistemindeki temel askıya alma ilkel öğesidir. ve mekanizmalarını anlamak, sağlam eşzamansız soyutlamalar tasarlamak için çok önemlidir.

yazarken co_await expr; Bir eşzamanlı işlem (coroutine) içinde, derleyici önce dönüştürme işlemini gerçekleştirir. expr "Beklenebilir" bir nesneye dönüştürmek, ya da içinden geçirerek promise.await_transform(expr) Böyle bir üye mevcutsa veya olduğu gibi kullanılıyorsa, "bekleyen" nesneyi belirler. Ardından, bir üyeyi çağırarak bunu yapar. operator co_await Beklenebilir olan üzerinde, üye olmayan operator co_awaitYa da, böyle bir operatör yoksa, beklenebilir nesnenin kendisini bekleyen nesne olarak ele almak.

Garsonun üç temel işlemi gerçekleştirmesi gerekmektedir: await_ready(), await_suspend(handle) hem de await_resume(). If await_ready() Eğer true döndürürse, eşzamanlı işlem askıya alınmaz ve doğrudan çağrılır. await_resume()Bu, halihazırda tamamlanmış işlemler için hızlı yollar sağlar. Eğer false döndürürse, eşzamanlı işlem askıya alınır, durumu çerçeveye kaydedilir ve await_suspend() Geçerli eşzamanlı işlemin tanıtıcısıyla çağrılır.

içeride await_suspend()Bekleyen taraf, eşzamanlı işlem işleyicisiyle ne yapacağına karar verebilir: Daha sonra bir yürütücüde devam ettirilmek üzere planlayın, başka bir eş zamanlı işlemi devam ettirin veya hatta aynı eş zamanlı işlemi hemen devam ettirin (dönüş türüne ve değerine bağlı olarak). await_suspend()Beklenen işlem tamamlandığında, sonunda biri arar. handle.resume()Bu noktada kontrol, hemen önceki haline geri döner. await_resume(), ve sonra await_resume() sonucunu verir co_await ifadesi.

Standart kütüphane iki önemsiz bekleme nesnesi içerir: std::suspend_always hem de std::suspend_never, genellikle kullanılanlar initial_suspend() hem de final_suspend() Tembel veya istekli başlatmayı ve sonda nasıl davranılacağını belirtmek için uygulamalar. Daha gelişmiş awaiter'lar, örneğin eşzamanlı işlemleri eşzamansız G/Ç API'lerine bağlamak için işlem başına durum tutabilir ve bu durum, askıya alma noktası boyunca eşzamanlı işlem çerçevesi içinde kalır.

co_yield ve jeneratör tarzı eşzamanlı işlemler

MKS co_yield ifade, üzerine inşa edilir co_await Jeneratör benzeri davranışları desteklemek için, Burada eş zamanlı işlem, çağıran kişi için tekrar tekrar değerler üretir ve bu çağıran kişi bu değerler üzerinde yineleme yapar.

kavramsal olarak, co_yield value; bir çağrıya dönüşüyor promise.yield_value(value) ve ardından bir uzaklaştırma, genellikle aracılığıyla co_await std::suspend_always veya benzer bir awaitable. Promise uygulaması, tüketicinin eşzamanlı işlem devam etmeden önce onu alabilmesi için üretilen değeri erişilebilir bir yerde (kopyalayarak, taşıyarak veya referans vererek) saklamaktan sorumludur.

Jeneratörleri uygulayan kütüphane kodu genellikle, mevcut üretilen değere erişmek ve standart yineleme protokolleriyle entegre olmak için yöntemler sunan bir promise türü tanımlar. örneğin sağlamak begin()/end() Handle sarmalayıcı üzerinde işlem yapılıyor ve her artışta altta yatan eşzamanlı işlem ilerletiliyor.

Hata yönetimi, askıda kalan referanslar ve ince detaylar

C++20 eşzamanlı işlevleri, promise'in sağladığı yapı aracılığıyla C++ istisna işleme ile entegre olur. unhandled_exception() yöntem olup, Derleyici, bir istisna eş zamanlı işlem gövdesinden dışarı çıktığında bu fonksiyonu çağırır. Ardından eş zamanlı işlem nihai olarak askıya alınır ve bu işlemin, hatanın eş zamanlı işlemin sonuç türüne sahip olan kişiye iletilmesini sağlaması beklenir.

Parametreler, oluşturulma aşamasında eşzamanlı işlem çerçevesine kopyalandığı veya referans verildiği için, Referans parametrelerine dikkat edilmelidir: eğer bu parametreler, eş zamanlı işlem yeniden başlatılmadan önce ömrü sona eren nesnelere referans veriyorsa, eş zamanlı işlem askıda kalan referansları çözebilir. Bu, eş zamanlı işleme özgü bir sorun değildir, ancak çerçevenin kalıcı yapısı, referans verilen nesnelerin ömrünün yanlışlıkla sona ermesini kolaylaştırır.

Standart, hata raporları aracılığıyla uç durumları açıklığa kavuşturmak üzere de geliştirilmiştir. Örneğin, bazı şeyleri geçersiz kılmak gibi. return_void Eş zamanlı işlemden çıkıldığında tanımsız davranış üretmek yerine, hatalı kurulumlar yapılmasına ve bu sayede co_await Lambda cisimleri gibi daha birçok bağlamda.

Birlikte ele alındığında, bu kurallar ve iyileştirmeler, daha üst düzey eşzamanlı işlem kütüphanelerinin güvenle üzerine inşa edilebileceği, nispeten düşük seviyeli ancak öngörülebilir bir model oluşturur. Basit jeneratörlerden, yürütücüler ve zamanlayıcılarla entegre edilmiş tam asenkron/await görev sistemlerine kadar çeşitlilik gösterir.

Yukarıdan bakıldığında, C'deki geçici assembly kodlarından C++20'nin yapılandırılmış, yığınsız, promise tabanlı eşzamanlı işlemlerine uzanan yolculuk görülüyor. Bu durum, sistem programcılarının güvendiği performans ve kontrolü kaybetmeden, karmaşık kontrol akışı, eşzamansız işlemler ve durum bilgisi içeren hesaplamaları ifade etmek için daha güvenli ve daha birleştirilebilir soyutlamalara doğru istikrarlı bir yönelimi yansıtmaktadır.

İlgili Mesajlar: