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_returnco_awaitco_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.
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_returnco_awaitdan co_yieldmaksudnya - 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;}
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.
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.
Konkurensi dan pemisahan masalah sama sekali tidak terkait. Coroutines tidak memberikan informasi untuk rutin ditangguhkan, mereka adalah rutinitas yang dapat dilanjutkan.
Jawaban:
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:
The
Generator
dipanggil, dan pertama kali dipanggil akan kembali0
. 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_yield
bersama 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:
co_yield
menangguhkan eksekusi fungsi, menyimpan status tersebut digenerator<int>
, lalu mengembalikan nilaicurrent
melaluigenerator<int>
.Anda dapat mengulang bilangan bulat yang dikembalikan.
co_await
Sementara 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, Andaco_await
melakukannya. Jika sudah siap, Anda segera melanjutkan; jika tidak, Anda menangguhkan sampai yang Anda tunggu-tunggu siap.load_data
adalah coroutine yang menghasilkanstd::future
saat sumber daya bernama dibuka dan kami berhasil mengurai ke titik di mana kami menemukan data yang diminta.open_resource
danread_line
s mungkin adalah coroutine asinkron yang membuka file dan membaca baris darinya. Theco_await
menghubungkan negara menangguhkan dan siapload_data
untuk 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_await
danco_yield
maksudnya - Saya telah melihat orang menggunakannya untuk mengimplementasikan ekspresi opsional monadik sehinggaco_await
pada opsional kosong secara otomatis menyebarkan status kosong ke opsional luar:dari pada
sumber
;;
.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.
sumber
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.
sumber