Apa coroutine di C ++ 20?

104

Apa itu coroutine ?

Dalam hal apa ini berbeda dari "Parallelism2" atau / dan "Concurrency2" (lihat gambar di bawah)?

Gambar di bawah ini dari ISOCPP.

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

masukkan deskripsi gambar di sini

Pavan Chandaka
sumber
3
Untuk menjawab "Dalam hal apa konsep coroutine berbeda dari paralelisme dan konkurensi ?" - en.wikipedia.org/wiki/Coroutine
Ben Voigt
3
Pengenalan coroutine yang sangat bagus dan mudah diikuti adalah presentasi James McNellis "Pengantar C ++ Coroutines" (Cppcon2016).
philsumuru
2
Terakhir, sebaiknya Anda juga membahas "Apa perbedaan coroutine di C ++ dari implementasi coroutine dan fungsi yang dapat dilanjutkan di bahasa lain?" (di mana artikel wikipedia yang ditautkan di atas, karena bahasa agnostik, tidak membahasnya)
Ben Voigt
1
siapa lagi yang membaca "karantina di C ++ 20" ini?
Sahib Yar

Jawaban:

199

Pada tingkat abstrak, Coroutines memisahkan gagasan memiliki status eksekusi dari gagasan memiliki rangkaian eksekusi.

SIMD (instruksi tunggal banyak data) memiliki beberapa "utas eksekusi" tetapi hanya satu status eksekusi (ini hanya berfungsi pada banyak data). Algoritma paralel bisa dibilang mirip seperti ini, karena Anda memiliki satu "program" yang dijalankan pada data yang berbeda.

Threading memiliki beberapa "utas eksekusi" dan beberapa status eksekusi. Anda memiliki lebih dari satu program, dan lebih dari satu rangkaian eksekusi.

Coroutines memiliki beberapa status eksekusi, tetapi tidak memiliki rangkaian eksekusi. Anda memiliki program, dan program tersebut memiliki status, tetapi tidak memiliki rangkaian eksekusi.


Contoh coroutine yang paling mudah adalah generator atau enumerable dari bahasa lain.

Dalam kode pseudo:

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

The Generatordipanggil, dan pertama kali dipanggil akan kembali 0. Statusnya diingat (seberapa banyak status bervariasi dengan penerapan coroutine), dan saat Anda memanggilnya kembali, status tersebut akan dilanjutkan. Jadi ini mengembalikan 1 kali berikutnya. Kemudian 2.

Akhirnya itu mencapai akhir loop dan jatuh dari akhir fungsi; coroutine selesai. (Apa yang terjadi di sini bervariasi berdasarkan bahasa yang kita bicarakan; dalam python, itu membuat pengecualian).

Coroutine membawa kemampuan ini ke C ++.

Ada dua jenis coroutine; stackful dan stackless.

Coroutine tanpa tumpukan hanya menyimpan variabel lokal dalam statusnya dan lokasi pelaksanaannya.

Coroutine yang bertumpuk menyimpan seluruh tumpukan (seperti utas).

Coroutine tanpa tumpukan bisa sangat ringan. Proposal terakhir yang saya baca pada dasarnya melibatkan penulisan ulang fungsi Anda menjadi sesuatu yang agak mirip lambda; semua variabel lokal masuk ke status objek, dan label digunakan untuk melompat ke / dari lokasi di mana coroutine "menghasilkan" hasil antara.

Proses menghasilkan nilai disebut "hasil", karena coroutine mirip seperti multithreading kooperatif; Anda memberikan titik eksekusi kembali ke pemanggil.

Boost memiliki implementasi coroutine yang bertumpuk; itu memungkinkan Anda memanggil fungsi untuk menghasilkan untuk Anda. Coroutine yang bertumpuk lebih bertenaga, tetapi juga lebih mahal.


Ada lebih banyak coroutine daripada generator sederhana. Anda dapat menunggu coroutine dalam coroutine, yang memungkinkan Anda membuat coroutine dengan cara yang berguna.

Coroutine, seperti if, loop dan panggilan fungsi, adalah jenis lain dari "goto terstruktur" yang memungkinkan Anda mengekspresikan pola berguna tertentu (seperti mesin status) dengan cara yang lebih alami.


Implementasi spesifik Coroutines di C ++ agak menarik.

Pada tingkat paling dasar, ia menambahkan beberapa kata kunci ke C ++:, co_return co_await co_yieldbersama dengan beberapa jenis pustaka yang bekerja dengannya.

Suatu fungsi menjadi coroutine dengan memiliki salah satu yang ada di tubuhnya. Jadi dari deklarasi mereka mereka tidak bisa dibedakan dari fungsi.

Ketika salah satu dari tiga kata kunci tersebut digunakan dalam badan fungsi, beberapa pemeriksaan standar yang diamanatkan dari jenis kembalian dan argumen terjadi dan fungsi tersebut diubah menjadi coroutine. Pemeriksaan ini memberi tahu compiler tempat menyimpan status fungsi saat fungsi dihentikan.

Coroutine paling sederhana adalah generator:

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

co_yieldmenangguhkan eksekusi fungsi, menyimpan status tersebut di generator<int>, lalu mengembalikan nilai currentmelalui generator<int>.

Anda dapat mengulang bilangan bulat yang dikembalikan.

co_awaitSementara itu, Anda dapat menggabungkan satu coroutine ke coroutine lainnya. Jika Anda menggunakan satu coroutine dan Anda membutuhkan hasil dari hal yang menunggu (seringkali coroutine) sebelum melanjutkan, Anda co_awaitmelakukannya. Jika sudah siap, Anda segera melanjutkan; jika tidak, Anda menangguhkan sampai yang Anda tunggu-tunggu siap.

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_dataadalah coroutine yang menghasilkan std::futuresaat sumber daya bernama dibuka dan kami berhasil mengurai ke titik di mana kami menemukan data yang diminta.

open_resourcedan read_lines mungkin adalah coroutine asinkron yang membuka file dan membaca baris darinya. The co_awaitmenghubungkan negara menangguhkan dan siap load_datauntuk kemajuan mereka.

Coroutine C ++ jauh lebih fleksibel daripada ini, karena diterapkan sebagai sekumpulan fitur bahasa minimal di atas tipe ruang pengguna. Jenis ruang pengguna secara efektif menentukan apa co_return co_awaitdan co_yield maksudnya - Saya telah melihat orang menggunakannya untuk mengimplementasikan ekspresi opsional monadik sehingga co_awaitpada opsional kosong secara otomatis menyebarkan status kosong ke opsional luar:

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

dari pada

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;
}
Yakk - Adam Nevraumont
sumber
26
Inilah salah satu penjelasan paling jelas tentang coroutine yang pernah saya baca. Membandingkannya dan membedakannya dari SIMD dan utas klasik adalah ide yang bagus.
Omnifarious
2
Saya tidak mengerti contoh add-opsional. std :: opsional <int> bukanlah objek yang bisa menunggu.
Jive Dadson
1
@ kata ya itu seharusnya mengembalikan 1 elemen. Mungkin perlu dipoles; jika kita ingin lebih dari satu jalur membutuhkan aliran kendali yang berbeda.
Yakk - Adam Nevraumont
1
@ Jika maaf, seharusnya begitu ;;.
Yakk - Adam Nevraumont
1
@ LF untuk fungsi sederhana seperti itu mungkin tidak ada perbedaan. Tetapi perbedaan yang saya lihat secara umum adalah bahwa coroutine mengingat titik masuk / keluar (eksekusi) di tubuhnya sedangkan fungsi statis memulai eksekusi dari awal setiap saat. Saya kira lokasi data "lokal" tidak relevan.
avp
21

Coroutine adalah seperti fungsi C yang memiliki beberapa pernyataan pengembalian dan ketika dipanggil untuk kedua kalinya tidak memulai eksekusi pada awal fungsi tetapi pada instruksi pertama setelah pengembalian yang dieksekusi sebelumnya. Lokasi eksekusi ini disimpan bersama dengan semua variabel otomatis yang akan hidup di tumpukan dalam fungsi non coroutine.

Penerapan coroutine eksperimental sebelumnya dari Microsoft memang menggunakan tumpukan yang disalin sehingga Anda bahkan dapat kembali dari fungsi bertingkat yang dalam. Namun versi ini ditolak oleh panitia C ++. Anda bisa mendapatkan implementasi ini misalnya dengan perpustakaan fiber Boosts.

Lothar
sumber
1

coroutine seharusnya (dalam C ++) fungsi yang dapat "menunggu" beberapa rutin lain selesai dan menyediakan apa pun yang diperlukan untuk menjalankan rutinitas yang ditangguhkan, dijeda, menunggu. fitur yang paling menarik bagi orang-orang C ++ adalah bahwa coroutine idealnya tidak mengambil ruang tumpukan ... C # sudah dapat melakukan sesuatu seperti ini dengan menunggu dan menghasilkan tetapi C ++ mungkin harus dibangun kembali untuk mendapatkannya.

concurrency sangat difokuskan pada pemisahan masalah di mana kekhawatiran adalah tugas yang seharusnya diselesaikan oleh program. pemisahan perhatian ini dapat dicapai dengan sejumlah cara ... biasanya berupa pendelegasian atau semacamnya. Ide konkurensi adalah bahwa sejumlah proses dapat berjalan secara independen (pemisahan perhatian) dan 'pendengar' akan mengarahkan apa pun yang dihasilkan oleh masalah yang terpisah itu ke mana pun ia seharusnya pergi. ini sangat bergantung pada beberapa jenis manajemen asynchronous. Ada sejumlah pendekatan untuk konkurensi termasuk pemrograman berorientasi Aspek dan lainnya. C # memiliki operator 'delegasi' yang bekerja dengan cukup baik.

paralelisme terdengar seperti konkurensi dan mungkin terlibat tetapi sebenarnya merupakan konstruksi fisik yang melibatkan banyak prosesor yang diatur dalam cara yang kurang lebih paralel dengan perangkat lunak yang mampu mengarahkan bagian kode ke prosesor yang berbeda di mana ia akan dijalankan dan hasilnya akan diterima kembali serentak.

Dr t
sumber
9
Konkurensi dan pemisahan masalah sama sekali tidak terkait. Coroutines tidak memberikan informasi untuk rutin ditangguhkan, mereka adalah rutinitas yang dapat dilanjutkan.
Ben Voigt