C ++ 20'deki eşgörünümler nelerdir?


104

İçinde koroutinler nelerdir ?

"Parallelism2" ve / ve "Concurrency2" den hangi yönlerden farklıdır (aşağıdaki resme bakın)?

Aşağıdaki resim ISOCPP'den alınmıştır.

https://isocpp.org/files/img/wg21-timeline-2017-03.png

görüntü açıklamasını buraya girin


3
" Eşdeğerler kavramı paralellik ve eşzamanlılıktan ne şekilde farklıdır ?" - en.wikipedia.org/wiki/Coroutine
Ben Voigt


3
Coroutine için çok iyi ve takip etmesi kolay bir giriş James McNellis'in “C ++ Coroutines'e Giriş” (Cppcon2016) sunumudur.
philsumuru

2
Nihayet o da kapağa iyi olurdu "Nasılsın eşyordamlar değiş tokuş eden kavramlar ve devam ettirilebilir fonksiyonların diğer dilde uygulamalarından farklı C ++?" (yukarıdaki bağlantılı wikipedia makalesi, dilden bağımsızdır, hitap etmez)
Ben Voigt

1
Bu "C ++ 20'deki karantina" yı başka kim okudu?
Sahib Yar

Yanıtlar:


203

Soyut bir düzeyde, Coroutines, bir yürütme durumuna sahip olma fikrini, bir yürütme iş parçacığına sahip olma fikrinden ayırdı.

SIMD (tek komut çoklu veri) birden fazla "yürütme evresine" sahiptir, ancak yalnızca bir yürütme durumuna sahiptir (yalnızca birden çok veri üzerinde çalışır). Muhtemelen paralel algoritmalar biraz buna benzer, çünkü farklı veriler üzerinde çalışan bir "program" a sahipsiniz.

İş parçacığı birden çok "yürütme iş parçacığı" ve birden çok yürütme durumuna sahiptir. Birden fazla programınız ve birden fazla yürütme iş parçacığınız var.

Coroutines'in birden fazla yürütme durumu vardır, ancak bir yürütme iş parçacığına sahip değildir. Bir programınız var ve programın durumu var, ancak yürütme iş parçacığı yok.


Coroutine'lerin en kolay örneği, diğer dillerden üreteçler veya numaralandırılabilirdir.

Sözde kodda:

function Generator() {
  for (i = 0 to 100)
    produce i
}

GeneratorDenilen ve bunu denir ilk kez döndürür 0. Durumu hatırlanır (eşgüdümlerin uygulanmasına göre ne kadar durum değişir) ve bir dahaki sefere onu çağırdığınızda kaldığı yerden devam eder. Yani bir dahaki sefere 1 döndürür. Ardından 2.

Sonunda döngünün sonuna ulaşır ve işlevin sonundan düşer; koroutin bitti. (Burada ne olduğu, bahsettiğimiz dile göre değişir; python'da bir istisna atar).

Coroutines bu özelliği C ++ 'ya getirir.

İki tür coroutine vardır; istifli ve istifsiz.

Yığınsız bir eşdizim, yerel değişkenleri yalnızca durumunda ve yürütme konumunda depolar.

Yığın dolu bir eşdizim, yığının tamamını (bir iş parçacığı gibi) depolar.

Yığınsız koroutinler son derece hafif olabilir. Son okuduğum öneri, temelde işlevinizi lambda gibi bir şeye yeniden yazmayı içeriyordu; tüm yerel değişkenler bir nesnenin durumuna gider ve etiketler, korutinin ara sonuçlar "ürettiği" konuma / buradan atlamak için kullanılır.

Bir değer üretme sürecine "verim" denir, çünkü korutinler, işbirlikli çoklu okumaya benzer; icra noktasını arayan kişiye geri veriyorsunuz.

Boost, yığın içeren eş dizimlerin bir uygulamasına sahiptir; sizin için bir fonksiyon çağırmanıza izin verir. Yığınlı eşgörünümler daha güçlüdür, ancak aynı zamanda daha pahalıdır.


Coroutine'ler için basit bir jeneratörden daha fazlası vardır. Coroutine'i yararlı bir şekilde oluşturmanıza izin veren bir coroutine'de bir coroutine bekleyebilirsiniz.

Coroutinler, if, döngüler ve işlev çağrıları gibi, belirli yararlı kalıpları (durum makineleri gibi) daha doğal bir şekilde ifade etmenize olanak tanıyan başka bir "yapılandırılmış goto" türüdür.


Coroutines'in C ++ 'da özel uygulaması biraz ilginçtir.

En temel düzeyinde, co_return co_await co_yieldonlarla çalışan bazı kitaplık türleriyle birlikte C ++: 'ya birkaç anahtar sözcük ekler .

Bir işlev, vücudunda bunlardan birine sahip olarak bir koroutine dönüşür. Dolayısıyla beyanlarından işlevlerden ayırt edilemezler.

Bu üç anahtar sözcükten biri bir işlev gövdesinde kullanıldığında, dönüş türünün ve argümanların bazı standart zorunlu incelemeleri gerçekleşir ve işlev bir coroutine dönüştürülür. Bu inceleme, derleyiciye işlev askıya alındığında işlev durumunu nerede saklayacağını söyler.

En basit korutin bir jeneratördür:

generator<int> get_integers( int start=0, int step=1 ) {
  for (int current=start; true; current+= step)
    co_yield current;
}

co_yieldişlevlerin yürütülmesini askıya alır, bu durumu içinde depolar generator<int>, ardından currentthrough the değerini döndürür generator<int>.

Döndürülen tamsayılar üzerinde döngü yapabilirsiniz.

co_awaitbu arada bir koroutini diğerine eklemenize izin verir. Eğer bir coroutine içindeyseniz ve ilerlemeden önce beklenebilir bir şeyin (genellikle bir koroutin) sonuçlarına ihtiyacınız varsa co_await, onun üzerindesiniz. Hazırlarsa, hemen ilerleyin; değilse, beklediğiniz bekleme süresi hazır olana kadar askıya alırsınız.

std::future<std::expected<std::string>> load_data( std::string resource )
{
  auto handle = co_await open_resouce(resource);
  while( auto line = co_await read_line(handle)) {
    if (std::optional<std::string> r = parse_data_from_line( line ))
       co_return *r;
  }
  co_return std::unexpected( resource_lacks_data(resource) );
}

load_datastd::futureadlandırılmış kaynak açıldığında bir oluşturan ve istenen verileri bulduğumuz noktaya kadar ayrıştırmayı başaran bir yordamdır .

open_resourceve read_lines büyük olasılıkla bir dosyayı açan ve ondan satır okuyan eşzamansız eşgüdümlerdir. co_awaitAskıda tutma ve hazır durumuna bağlayan load_databunların ilerlemesinin.

C ++ coroutines, kullanıcı alanı türlerinin üzerinde minimum bir dil özellikleri kümesi olarak uygulandıkları için bundan çok daha esnektir. Kullanıcı alanı türleri ne co_return co_awaitve ne co_yield anlama geldiğini etkili bir şekilde tanımlar - İnsanların bunu isteğe bağlı tek ifadeleri uygulamak için kullandığını gördüm, öyle ki a co_awaitboş bir isteğe bağlı olarak boş durumu otomatik olarak dış isteğe bağlı olarak yayar:

modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
  return (co_await a) + (co_await b);
}

onun yerine

std::optional<int> add( std::optional<int> a, std::optional<int> b ) {
  if (!a) return std::nullopt;
  if (!b) return std::nullopt;
  return *a + *b;
}

26
Bu, şimdiye kadar okuduğum doğru çizgilerin en net açıklamalarından biri. Bunları SIMD ve klasik iş parçacıklarıyla karşılaştırmak ve birbirinden ayırmak mükemmel bir fikirdi.
Omnifarious

2
İsteğe bağlı eklenti örneğini anlamıyorum. std :: isteğe bağlı <int> beklenebilir bir nesne değil.
Jive Dadson'ın

1
@mord evet, 1 elementi döndürmesi gerekiyor. Cilalamaya ihtiyaç duyabilir; Birden fazla hat istiyorsak farklı bir kontrol akışına ihtiyaç duyarız.
Yakk - Adam Nevraumont

1
@lf üzgünüm, olması gerekiyordu ;;.
Yakk - Adam Nevraumont

1
@LF bu kadar basit işlev için belki bir fark yoktur. Ancak genel olarak gördüğüm fark, bir coroutine'in gövdesindeki giriş / çıkış (yürütme) noktasını hatırlaması ve statik bir fonksiyonun her seferinde çalıştırmayı baştan başlatmasıdır. "Yerel" verilerin yeri sanırım ilgisiz.
avp

21

Bir koroutin, birden fazla dönüş ifadesine sahip olan ve 2. kez çağrıldığında, işlevin başlangıcında değil, önceki çalıştırılan dönüşten sonraki ilk komutta çalıştırmaya başlamayan bir C işlevi gibidir. Bu yürütme konumu, eşgüdümlü olmayan işlevlerde yığında yaşayan tüm otomatik değişkenlerle birlikte kaydedilir.

Microsoft'un önceki deneysel bir coroutine uygulaması, derinlemesine iç içe geçmiş işlevlerden geri dönebilmeniz için kopyalanmış yığınları kullanıyordu. Ancak bu sürüm C ++ komitesi tarafından reddedildi. Bu uygulamayı örneğin Boosts fiber kitaplığı ile elde edebilirsiniz.


1

Coroutinlerin, başka bir rutinin tamamlanması için "bekleyebilen" ve askıya alınan, duraklatılan, bekleyen, rutinin devam etmesi için gereken her şeyi sağlayan (C ++ 'da) işlevler olması beklenir. C ++ üyeleri için en ilginç olan özellik, eşgüdümlerin ideal olarak yığın alanı kullanmamasıdır ... C # zaten böyle bir şeyi bekleme ve verimle yapabilir, ancak C ++ 'nın onu almak için yeniden oluşturulması gerekebilir.

eşzamanlılık, bir endişenin programın tamamlaması gereken bir görev olduğu durumlarda büyük ölçüde endişelerin ayrılmasına odaklanır. bu kaygıların ayrılması birkaç yolla başarılabilir ... genellikle bir tür yetkilendirme olabilir. Eşzamanlılık fikri, bir dizi sürecin bağımsız olarak çalışabileceği (endişelerin ayrılması) ve bir 'dinleyicinin' bu ayrılan endişeler tarafından üretilen her şeyi gitmesi gereken yere yönlendireceği yönündedir. bu büyük ölçüde bir tür eşzamansız yönetime bağlıdır. Aspect odaklı programlama ve diğerleri dahil olmak üzere eşzamanlılık için bir dizi yaklaşım vardır. C #, oldukça iyi çalışan 'delege' operatörüne sahiptir.

paralellik eşzamanlılık gibi görünür ve söz konusu olabilir, ancak aslında kodun bölümlerini çalıştırılacağı ve sonuçların geri alınacağı farklı işlemcilere yönlendirebilen yazılımla aşağı yukarı paralel bir şekilde düzenlenmiş birçok işlemciyi içeren fiziksel bir yapıdır. eşzamanlı olarak.


9
Eşzamanlılık ve endişelerin ayrılması tamamen ilgisizdir. Eşyordamlar askıya rutin bilgi sağlamak için değil, bunlar şunlardır devam ettirilebilir rutinleri.
Ben Voigt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.