Yazılım Mühendisliği: Dian-Lun Li tarafından Coroutine’lere Kısa Bir Giriş

Adanali

Active member
Yazılım Mühendisliği: Dian-Lun Li tarafından Coroutine’lere Kısa Bir Giriş


  1. Yazılım Mühendisliği: Dian-Lun Li tarafından Coroutine’lere Kısa Bir Giriş

Bugün blogumda görev zamanlayıcı hakkında bir mini diziye başlıyorum. Bu mini dizinin başlangıç noktası, Dian-Lun Li’nin giderek daha karmaşık hale gelen basit bir planlayıcısıdır.

Duyuru








Rainer Grimm uzun yıllardır yazılım mimarı, ekip ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanıyor, aynı zamanda özel konferanslarda sık sık konuşmaktan da hoşlanıyor. Modern C++ adlı blogunda C++ tutkusunu yoğun bir şekilde ele alıyor.













Zaten koroutinler hakkında yaklaşık 15 makale yazdım. Eşyordam teorisini açıklar ve bunları çeşitli şekillerde uygularlar. Ancak hala eşyordamların önemsiz olmayan bir kullanım durumuna sezgisel bir giriş bulmakta zorlanıyorum. Bu nedenle Dian-Lun Li’nin CppCon 2022’deki konuşmasını dinlediğimde çok mutlu oldum: “İş Parçacığı Planlama Gösterimi Yoluyla C++ Coroutines’e Giriş”.

Bugün Dian-Lun Li’nin konuk makalesine yer vermenin mutluluğunu yaşıyorum. Görevleri dağıtan basit bir zamanlayıcı uygulamak için sezgisel olarak eşyordamları tanıtacaktır. Bu zamanlayıcıyı daha sonraki deneyler için bir başlangıç noktası olarak kullanacağım.

C++ eşyordamlarına giriş


Bir eşyordam, arayan kişi tarafından kesintiye uğratılabilen ve devam ettirilebilen bir işlevdir. Baştan sona sırayla çalışan normal işlevlerin aksine, eşyordamlar yürütmeyi kontrollü bir şekilde duraklatabilir ve devam ettirebilir. Bu, eşzamanlı görünen ancak eşzamansız işlemleri çağıran iş parçacığını engellemeden verimli bir şekilde yönetebilen kod yazmamıza olanak tanır. Çok yönlülüğü nedeniyle bir C++ eşyordamının uygulanması biraz zor olabilir. C++ eşyordamlarında, bir eşyordamın davranışı birçok yolla ayarlanabilir. Örneğin, bir eşyordamın başlangıçta mı yoksa çıkışta mı durması gerektiğine karar verebilirsiniz. Ancak bu kesintilerin eşyordam içinde tam olarak ne zaman ve nerede meydana geleceğini de belirleyebilirsiniz. Açıklamak için basit bir örnekle başlamak istiyorum:


// simpleCoroutine.cpp

#include <coroutine>&#13;
#include <iostream>&#13;
&#13;
struct MyCoroutine { // (1)&#13;
struct promise_type {&#13;
MyCoroutine get_return_object() {&#13;
return std::coroutine_handle<promise_type>::from_promise(*this);&#13;
}&#13;
std::suspend_always initial_suspend() {&#13;
return {};&#13;
}&#13;
std::suspend_always final_suspend() noexcept {&#13;
return {};&#13;
}&#13;
void return_void() {}&#13;
void unhandled_exception() {}&#13;
};&#13;
MyCoroutine(std::coroutine_handle<promise_type> handle): handle{handle} {}&#13;
&#13;
void resume() { &#13;
handle.resume(); &#13;
}&#13;
void destroy() { &#13;
handle.destroy(); &#13;
}&#13;
&#13;
std::coroutine_handle<promise_type> handle;&#13;
};&#13;
&#13;
MyCoroutine simpleCoroutine() { // (2)&#13;
std::cout << "Start coroutinen";&#13;
co_await std::suspend_always{};&#13;
std::cout << "Resume coroutinen";&#13;
}&#13;
&#13;
int main() {&#13;
MyCoroutine coro = simpleCoroutine();&#13;
std::cout << "Coroutine is not executed yetn";&#13;
coro.resume();&#13;
std::cout << "Suspend coroutinen";&#13;
coro.resume();&#13;
coro.destroy();&#13;
return 0;&#13;
}


Bu örnek kod, C++ eşyordamlarının temel kullanımını gösterir. Bunu uygulamak için dört temel bileşeni anlamanız gerekir: Eşyordam, Söz Türü, Beklenebilir ve Eşyordam Tutacağı. Aşağıdaki bölümlerde her bileşeni örnek kod kullanarak açıklayacağım.

Eşyordamlar


C++’da eşyordamlar anahtar sözcüklerle tanımlanır. co_return, co_await VE co_yield uygulandı. Bu anahtar kelimeler, geliştiricilerin eşzamansız davranışları yapılandırılmış ve sezgisel bir şekilde ifade etmelerine olanak tanır. Örnek koroutinde simpleCoroutine arayacağım co_await std::suspend always{} koroutini durdurmak için. std::suspend_always C++ standardı tarafından sağlanan ve eşyordamı her zaman askıya alan, beklenebilir bir özelliktir.

Fonksiyonu çağırırken simpleCoroutineeşyordam hemen çalışmayacaktır. Bunun yerine Promise türünü tanımlayan bir Coroutine nesnesi alırsınız. (2) işlevi tanımlar simpleCoroutineOrası MyCoroutinenesne geri döner. (1)’de sınıfı tanımlıyorum MyCoroutine ve Söz türü. Bir eşyordam fonksiyonunun çağrılmasının onu hemen yürütmemesi gerçeği, C++ eşyordamının esnek olacak şekilde tasarlanmış olmasıdır. C++ Coroutine ile bir eşyordamın ne zaman ve nasıl başlatılıp sonlandırılacağına karar verebilirsiniz. Bu içeride promise_type Tanımlanmıştır.

Bir nevi söz


A promise_type bir eşyordamın davranışını kontrol eder. İşte en önemli görevler promise_type:

  • Eşyordam nesnesini oluşturma: işlev get_return_object eşyordamın bir örneğini oluşturur ve onu arayana geri gönderir.
  • Süspansiyon kontrolü: işlevler initial_suspend VE final_suspend eşyordamın başlangıçta ve sonda durması mı yoksa devam etmesi mi gerektiğini belirleyin. Eşyordamın davranışını belirleyen güvenilirleri döndürürler.
  • Dönüş değerlerinin yönetimi: işlev return_value tamamlandıktan sonra eşyordamın döndürdüğü değeri ayarlar. Eşyordamın arayanın alabileceği bir sonuç döndürmesine izin verir. Kullandığım örnek kodda return_voidbu eşyordamın dönüş değeri olmadığını belirtmek için.
  • İstisna yönetimi: işlev unhandled_exception eşyordam içinde işlenmeyen bir istisna oluştuğunda çağrılır. İstisnaları zarif bir şekilde ele alacak bir mekanizma sağlar.
Peki nerede olacak? promise_type kullanılmış? Örnek kodda “söz” kelimesini bulamıyorum. Bir eşyordam yazdığınızda derleyici kodunuzu biraz farklı görür. Derleyicinin basitleştirilmiş araması simpleCoroutine takip ediliyor:


MyCoroutine simpleCoroutine() {&#13;
MyCoroutine::promise_type p();&#13;
MyCoroutine coro_obj = p.get_return_object();&#13;
&#13;
try {&#13;
co_await p.inital_suspend();&#13;
std::cout << "Start coroutinen";&#13;
co_await std::suspend_always{};&#13;
std::cout << "Resume coroutinen";&#13;
} catch(...) {&#13;
p.unhandled_exception();&#13;
}&#13;
co_await p.final_suspend();&#13;
}


Bu nedenle gerekir promise_type sınıfta MyCoroutine Tanımlanmış olmak. öz simpleCoroutine çağrılırsa derleyici bir tane yaratır promise_type ve Çağrı yap get_return_object() Bu konuda MyCoroutine-Oluşturulacak nesne. Derleyici eşyordam gövdesinden önce çağırır initial_suspend eşyordamın başlangıçta duraklatılıp duraklatılmayacağını belirlemek için. Sonunda aradı final_suspend yürütmenin sonunda durdurulup durdurulmayacağına karar vermek için. DSÖ promise_type ve karşılık gelen işlevler tanımlanmadıysa, bir derleyici hatası ortaya çıkar.

Güvenilir


Beklenebilir, bir askı noktasının davranışını kontrol eder. Beklenebilir bir fonksiyon için üç fonksiyonun tanımlanması gerekir:

  • await_ready: Bu fonksiyon eşyordamın kesintisiz olarak devam edip edemeyeceğini belirler. O yapmalı true İşlem hemen devam edebilecekse geri dönün veya falsebir kesinti gerektiğinde. Bu yöntem, işlemin senkronize olarak tamamlanacağının bilindiği durumlarda kesinti maliyetini ortadan kaldırabilecek bir optimizasyondur.
  • await_suspend: Bu fonksiyonla bir askı noktasının davranışını tam olarak kontrol edebilirsiniz. Kullanıcıların daha sonra ortakyordamı devam ettirebilmesi veya yok edebilmesi için mevcut eşyordamın tanıtıcısını iletin. Bu işlev için üç dönüş türü vardır:
  1. boş: Koroutini askıya alıyoruz. Kontrol, mevcut eşyordamı arayan kişiye anında geri verilir.
  2. bool: İLE true mevcut eşyordamı keseriz ve kontrolü arayana geri veririz; İLE false mevcut koroutine devam ediyoruz.
  3. coroutine_handle: Geçerli eşyordamı askıya alırız ve döndürülen eşyordam tanıtıcısını sürdürürüz. Buna asimetrik transfer de denir.
  • await_resume: Bu işlev, amaçlanan işlem tamamlandığında eşyordama hangi değerin döndürüleceğini belirtir. Eşyordamın yürütülmesine devam eder ve beklenen sonucu döndürür. Hiçbir sonuç beklenmiyorsa veya gerekmiyorsa bu işlev boş olabilir ve void geri vermek.
Peki bu özellikler nerede kullanılıyor? Derleyicinin bakış açısını bir kez daha ele alalım. Eğer sen co_await std:suspend_always{} çağrılar, derleyici bunu aşağıdaki koda dönüştürür:


auto&& awaiter = std::suspend_always{};&#13;
if(!awaiter.await_ready()) {&#13;
awaiter.await_suspend(std::coroutine_handle<>...);&#13;
//<suspend/resume>&#13;
}&#13;
awaiter.await_resume();


Bu yüzden tüm bu fonksiyonları tanımlamanız gerekiyor. THE std::suspend_always C++’ta yerleşik olarak işlevleri şu şekilde tanımlayan bir garsondur:


struct suspend_always {&#13;
constexpr bool await_ready() const noexcept { return false; }&#13;
constexpr void await_suspend(coroutine_handle<>) const noexcept {}&#13;
constexpr void await_resume() const noexcept {}&#13;
};&#13;



Koroutin tanıtıcısı


Eşyordam tutamaçları bir eşyordamın durumunu ve yaşam döngüsünü yönetmek için kullanılır. Eşyordamları açıkça çağırmak, sürdürmek ve yok etmek için bir yol sağlarlar. Aradığım örnekte handle.resume() eşyordam e’ye devam etmek handle.destroy()koroutini yok etmek için.

Programı çalıştırmanın sonucu şöyle görünür:








Sıradaki ne?


Söz verildiği gibi, Dian-Lun Li’nin bu makalesi eşyordamlara kısa bir giriş niteliğindeydi. Bir sonraki makalede Dian-Lun, C++ eşyordamları için tek iş parçacıklı bir zamanlayıcı uygulamak üzere teoriyi uyguluyor.

Modern C++ eğitimi ile entegre programlama



(kendim)



Haberin Sonu
 
Üst