Saya cukup akrab dengan C ++ 11's std::thread
, std::async
dan std::future
komponen (misalnya lihat jawaban ini ), yang lurus ke depan.
Namun, saya tidak dapat memahami apa std::promise
itu, apa yang dilakukannya dan dalam situasi apa yang paling baik digunakan. Dokumen standar itu sendiri tidak mengandung banyak informasi di luar sinopsis kelasnya, dan juga tidak hanya :: utas .
Dapatkah seseorang tolong memberikan contoh singkat dan ringkas tentang situasi di mana suatu std::promise
dibutuhkan dan di mana itu adalah solusi yang paling idiomatis?
c++
multithreading
c++11
promise
standard-library
Kerrek SB
sumber
sumber
std::promise
manastd::future
asalnya.std::future
adalah apa yang memungkinkan Anda untuk mengambil nilai yang telah dijanjikan kepada Anda. Ketika Anda memanggilget()
masa depan, ia menunggu sampai pemiliknyastd::promise
menentukan nilainya (dengan memanggilset_value
janji). Jika janji itu dihancurkan sebelum nilai ditentukan, dan Anda kemudian memanggilget()
masa depan yang terkait dengan janji itu, Anda akan mendapatkanstd::broken_promise
pengecualian karena Anda dijanjikan nilai, tetapi tidak mungkin bagi Anda untuk mendapatkannya.std::broken_promise
adalah pengidentifikasi terbaik di perpustakaan standar. Dan tidak adastd::atomic_future
.Jawaban:
Dalam kata-kata [futures.state] a
std::future
adalah objek kembali asinkron ("objek yang membaca hasil dari keadaan bersama") danstd::promise
merupakan penyedia asinkron ("objek yang memberikan hasil ke keadaan bersama") yaitu a janji adalah hal yang Anda tetapkan hasilnya, sehingga Anda bisa mendapatkannya dari masa depan yang terkait.Penyedia asinkron adalah yang awalnya menciptakan keadaan bersama yang merujuk masa depan.
std::promise
adalah satu jenis penyedia asinkron,std::packaged_task
yang lain, dan detail internalstd::async
yang lain. Masing-masing dapat membuat status bersama dan memberi Andastd::future
yang membagikan status itu, dan dapat membuat negara siap.std::async
adalah utilitas kenyamanan tingkat tinggi yang memberi Anda objek hasil asinkron dan secara internal menangani pembuatan penyedia asinkron dan membuat status bersama siap ketika tugas selesai. Anda bisa meniru itu denganstd::packaged_task
(ataustd::bind
danstd::promise
) dan astd::thread
tetapi lebih aman dan lebih mudah digunakanstd::async
.std::promise
adalah level yang sedikit lebih rendah, untuk ketika Anda ingin meneruskan hasil asinkron ke masa depan, tetapi kode yang membuat hasil siap tidak dapat dibungkus dalam satu fungsi yang cocok untuk diteruskanstd::async
. Misalnya, Anda mungkin memiliki array beberapapromise
dan terkaitfuture
dan memiliki satu utas yang melakukan beberapa perhitungan dan menetapkan hasil pada setiap janji.async
hanya akan memungkinkan Anda untuk mengembalikan satu hasil, untuk mengembalikan beberapa Anda perlu meneleponasync
beberapa kali, yang mungkin menghabiskan sumber daya.sumber
future
adalah contoh konkret dari objek kembali asinkron , yang merupakan objek yang membaca hasil yang dikembalikan secara tidak sinkron, melalui keadaan bersama. Apromise
adalah contoh konkret dari penyedia asinkron , yang merupakan objek yang menulis nilai ke status bersama, yang dapat dibaca secara tidak sinkron. Maksud saya apa yang saya tulis.Saya mengerti situasinya sedikit lebih baik sekarang (dalam jumlah kecil karena jawaban di sini!), Jadi saya pikir saya menambahkan sedikit artikel saya sendiri.
Ada dua konsep yang berbeda, meskipun terkait, dalam C ++ 11: Asynchronous computation (fungsi yang disebut di tempat lain), dan eksekusi bersamaan ( utas , sesuatu yang bekerja secara bersamaan). Keduanya adalah konsep yang agak ortogonal. Komputasi Asynchronous hanya rasa yang berbeda dari panggilan fungsi, sedangkan utas adalah konteks eksekusi. Utas berguna dalam hak mereka sendiri, tetapi untuk tujuan diskusi ini, saya akan memperlakukan mereka sebagai detail implementasi.
Ada hierarki abstraksi untuk perhitungan asinkron. Sebagai contoh, misalkan kita memiliki fungsi yang mengambil beberapa argumen:
Pertama, kami memiliki template
std::future<T>
, yang mewakili nilai tipe masa depanT
. Nilai dapat diambil melalui fungsi anggotaget()
, yang secara efektif menyinkronkan program dengan menunggu hasilnya. Atau, dukungan masa depanwait_for()
, yang dapat digunakan untuk menyelidiki apakah hasilnya sudah tersedia atau tidak. Futures harus dianggap sebagai pengganti drop-in asinkron untuk tipe pengembalian biasa. Untuk fungsi contoh kita, kita mengharapkan astd::future<int>
.Sekarang, ke hierarki, dari level tertinggi ke level terendah:
std::async
: Cara paling mudah dan mudah untuk melakukan perhitungan asinkron adalah melaluiasync
templat fungsi, yang segera mengembalikan pencocokan di masa depan:Kami memiliki sedikit kontrol atas detail. Secara khusus, kita bahkan tidak tahu apakah fungsinya dijalankan bersamaan, berseri
get()
, atau dengan ilmu hitam lainnya. Namun, hasilnya mudah diperoleh saat dibutuhkan:Kita sekarang dapat mempertimbangkan bagaimana menerapkan sesuatu seperti
async
, tetapi dengan cara itu kita kendalikan. Sebagai contoh, kami mungkin bersikeras bahwa fungsi dijalankan di utas terpisah. Kami sudah tahu bahwa kami dapat menyediakan utas terpisah dengan carastd::thread
kelas.Level abstraksi selanjutnya yang lebih rendah melakukan hal itu:
std::packaged_task
. Ini adalah templat yang membungkus suatu fungsi dan menyediakan masa depan untuk nilai pengembalian fungsi, tetapi objek itu sendiri dapat dipanggil, dan memanggilnya adalah atas kebijaksanaan pengguna. Kita dapat mengaturnya seperti ini:Masa depan menjadi siap setelah kami memanggil tugas dan panggilan selesai. Ini adalah pekerjaan ideal untuk utas terpisah. Kami hanya harus memastikan untuk memindahkan tugas ke utas:
Utas mulai berjalan segera. Kita dapat
detach
melakukannya, atau memilikinyajoin
di akhir ruang lingkup, atau kapan saja (misalnya menggunakanscoped_thread
pembungkus Anthony Williams , yang seharusnya ada di perpustakaan standar). Namun, detail penggunaannyastd::thread
tidak menjadi perhatian kami di sini; pastikan untuk bergabung atau melepaskanthr
akhirnya. Yang penting adalah bahwa setiap kali panggilan fungsi selesai, hasil kami siap:Sekarang kita turun ke level terendah: Bagaimana kita mengimplementasikan tugas yang dikemas? Di sinilah yang
std::promise
datang. Janji adalah blok bangunan untuk berkomunikasi dengan masa depan. Langkah-langkah utamanya adalah ini:Utas panggilan membuat janji.
Utang panggilan mendapatkan masa depan dari janji.
Janji, bersama dengan argumen fungsi, dipindahkan ke utas terpisah.
Utas baru menjalankan fungsi dan memenuhi janji.
Utas asli mengambil hasilnya.
Sebagai contoh, inilah "tugas terpaket" kami sendiri:
Penggunaan template ini pada dasarnya sama dengan
std::packaged_task
. Perhatikan bahwa memindahkan seluruh tugas berarti memindahkan janji. Dalam situasi yang lebih ad-hoc, orang juga bisa memindahkan objek janji secara eksplisit ke utas baru dan menjadikannya argumen fungsi dari fungsi utas, tetapi pembungkus tugas seperti yang di atas sepertinya merupakan solusi yang lebih fleksibel dan tidak terlalu mengganggu.Membuat pengecualian
Janji terkait erat dengan pengecualian. Antarmuka janji saja tidak cukup untuk menyampaikan keadaannya sepenuhnya, sehingga pengecualian dilemparkan setiap kali operasi pada janji tidak masuk akal. Semua pengecualian bertipe
std::future_error
, yang berasal daristd::logic_error
. Pertama, deskripsi beberapa kendala:Janji yang dibangun secara default tidak aktif. Janji tidak aktif bisa mati tanpa konsekuensi.
Sebuah janji menjadi aktif ketika masa depan diperoleh melalui
get_future()
. Namun, hanya satu masa depan yang dapat diperoleh!Sebuah janji harus dipenuhi melalui
set_value()
atau memiliki pengecualian yang ditetapkanset_exception()
sebelum masa hidupnya berakhir jika masa depannya ingin dikonsumsi. Janji yang puas dapat mati tanpa konsekuensi, danget()
tersedia di masa depan. Sebuah janji dengan pengecualian akan memunculkan eksepsi yang tersimpan atas panggilanget()
di masa depan. Jika janji itu mati tanpa nilai atau pengecualian, menyerukanget()
masa depan akan menimbulkan pengecualian "janji rusak".Berikut ini adalah serangkaian uji untuk menunjukkan berbagai perilaku luar biasa ini. Pertama, harness:
Sekarang ke tes.
Kasus 1: Janji tidak aktif
Kasus 2: Janji aktif, tidak digunakan
Kasus 3: Terlalu banyak masa depan
Kasus 4: Janji yang dipuaskan
Kasus 5: Terlalu banyak kepuasan
Pengecualian yang sama dilemparkan jika ada lebih dari satu baik dari
set_value
atauset_exception
.Kasus 6: Pengecualian
Kasus 7: Patah janji
sumber
std::function
Memiliki banyak konstruktor; tidak ada alasan untuk tidak memaparkannya kepada konsumenmy_task
.get()
masa depan menimbulkan pengecualian. Saya akan mengklarifikasi ini dengan menambahkan "sebelum dihancurkan"; tolong beri tahu saya jika itu cukup jelas.got()
sayafuture
grokking perpustakaan dukungan thread padapromise
penjelasan luar biasa Anda!Bartosz Milewski menyediakan penulisan yang bagus.
std :: janji adalah salah satu dari bagian-bagian ini.
...
Jadi, jika Anda ingin menggunakan masa depan, Anda berakhir dengan janji yang Anda gunakan untuk mendapatkan hasil dari pemrosesan asinkron.
Contoh dari halaman tersebut adalah:
sumber
Dalam perkiraan kasar Anda dapat mempertimbangkan
std::promise
sebagai ujung lain dari astd::future
(ini salah , tetapi untuk ilustrasi Anda dapat berpikir seolah-olah itu). Konsumen akhir saluran komunikasi akan menggunakan astd::future
untuk mengkonsumsi datum dari negara bersama, sedangkan utas produsen akan menggunakan astd::promise
untuk menulis ke negara bersama.sumber
std::async
dapat secara konseptual (ini tidak diamanatkan oleh standar) dipahami sebagai fungsi yang menciptakanstd::promise
, mendorongnya ke dalam kumpulan benang (macam, mungkin kumpulan benang, mungkin utas baru, ...) dan mengembalikan terkaitstd::future
dengan penelepon. Di sisi klien Anda akan menunggu distd::future
dan utas di ujung yang lain akan menghitung hasilnya dan menyimpannya distd::promise
. Catatan: standar mensyaratkan keadaan bersama danstd::future
tetapi tidak adanya astd::promise
dalam kasus penggunaan khusus ini.std::future
tidak akan memanggiljoin
utas, ia memiliki pointer ke keadaan bersama yang merupakan buffer komunikasi yang sebenarnya. Keadaan bersama memiliki mekanisme sinkronisasi (mungkinstd::function
+std::condition_variable
untuk mengunci penelepon hinggastd::promise
terpenuhi. Eksekusi utas adalah ortogonal untuk semua ini, dan dalam banyak implementasi Anda mungkin menemukan bahwastd::async
tidak dijalankan oleh utas baru yang kemudian bergabung, tetapi alih-alih oleh kumpulan utas yang masa hidupnya diperpanjang hingga akhir programstd::async
adalah bahwa perpustakaan runtime dapat membuat keputusan yang tepat untuk Anda sehubungan dengan jumlah utas untuk dibuat dan dalam kebanyakan kasus saya akan mengharapkan runtimes yang menggunakan kumpulan utas. Saat ini VS2012 tidak menggunakan kumpulan thread di bawah tenda, dan itu tidak melanggar aturan as-if . Perhatikan bahwa ada sangat sedikit jaminan yang harus dipenuhi untuk ini -jika .std::promise
adalah saluran atau jalur untuk informasi yang akan dikembalikan dari fungsi async.std::future
adalah mekanisme sinkronisasi yang membuat pemanggil menunggu sampai nilai kembali yang dibawa dalamstd::promise
siap (artinya nilainya diatur di dalam fungsi).sumber
Sebenarnya ada 3 entitas inti dalam pemrosesan asinkron. C ++ 11 saat ini berfokus pada 2 diantaranya.
Hal-hal inti yang Anda perlukan untuk menjalankan beberapa logika secara tidak sinkron adalah:
C ++ 11 menyebut hal-hal yang saya bicarakan dalam (1)
std::promise
, dan hal-hal dalam (3)std::future
.std::thread
adalah satu-satunya hal yang disediakan untuk umum (2). Ini sangat disayangkan karena program nyata perlu mengelola sumber daya thread & memori, dan sebagian besar akan menginginkan tugas untuk berjalan di kolam utas alih-alih membuat & menghancurkan utas untuk setiap tugas kecil (yang hampir selalu menyebabkan hit kinerja yang tidak perlu dengan sendirinya dan dapat dengan mudah membuat sumber daya kelaparan yang bahkan lebih buruk).Menurut Herb Sutter dan yang lainnya dalam kepercayaan otak C ++ 11, ada rencana tentatif untuk menambahkan bahwa
std::executor
- seperti di Jawa - akan menjadi dasar untuk kumpulan benang dan secara logis pengaturan yang sama untuk (2). Mungkin kita akan melihatnya di C ++ 2014, tetapi taruhan saya lebih seperti C ++ 17 (dan Tuhan membantu kami jika mereka merusak standar untuk ini).sumber
A
std::promise
dibuat sebagai titik akhir untuk pasangan janji / masa depan danstd::future
(dibuat dari std :: janji menggunakanget_future()
metode) adalah titik akhir lainnya. Ini adalah metode satu suntikan sederhana yang menyediakan cara untuk menyinkronkan dua utas sebagai satu utas menyediakan data ke utas lain melalui pesan.Anda dapat menganggapnya sebagai satu utas menciptakan janji untuk memberikan data dan utas lainnya mengumpulkan janji di masa depan. Mekanisme ini hanya bisa digunakan satu kali.
Mekanisme janji / masa depan hanya satu arah, dari utas yang menggunakan
set_value()
metode astd::promise
ke utas yang menggunakanget()
astd::future
untuk menerima data. Pengecualian dihasilkan jikaget()
metode masa depan dipanggil lebih dari sekali.Jika benang dengan
std::promise
belum digunakanset_value()
untuk memenuhi janjinya maka ketika panggilan thread keduaget()
daristd::future
untuk mengumpulkan janji, benang kedua akan masuk ke keadaan menunggu sampai janji itu dipenuhi oleh thread pertama denganstd::promise
ketika menggunakanset_value()
metode untuk mengirim data.Dengan coroutine yang diusulkan dari Spesifikasi Teknis N4663 Bahasa Pemrograman - Ekstensi C ++ untuk Coroutine dan dukungan kompiler Visual Studio 2017 C ++
co_await
, juga dimungkinkan untuk menggunakanstd::future
danstd::async
menulis fungsionalitas coroutine. Lihat diskusi dan contoh di https://stackoverflow.com/a/50753040/1466970 yang memiliki sebagai satu bagian yang membahas penggunaanstd::future
denganco_await
.Contoh kode berikut, aplikasi konsol Visual Studio 2013 sederhana, menunjukkan menggunakan beberapa kelas / templat C ++ 11 concurrency dan fungsi lainnya. Ini menggambarkan penggunaan janji / masa depan yang bekerja dengan baik, utas otonom yang akan melakukan beberapa tugas dan berhenti, dan penggunaan di mana perilaku yang lebih sinkron diperlukan dan karena kebutuhan untuk banyak pemberitahuan, pasangan janji / masa depan tidak berfungsi.
Satu catatan tentang contoh ini adalah penundaan yang ditambahkan di berbagai tempat. Penundaan ini ditambahkan hanya untuk memastikan bahwa berbagai pesan yang dicetak ke konsol menggunakan
std::cout
akan jelas dan bahwa teks dari beberapa utas tidak akan berbaur.Bagian pertama dari
main()
membuat tiga utas tambahan dan menggunakanstd::promise
danstd::future
untuk mengirim data antara utas. Poin yang menarik adalah di mana utas utama memulai utas, T2, yang akan menunggu data dari utas utama, melakukan sesuatu, dan kemudian mengirim data ke utas ketiga, T3, yang kemudian akan melakukan sesuatu dan mengirim data kembali ke utas. utas utama.Bagian kedua dari
main()
menciptakan dua utas dan satu set antrian untuk memungkinkan beberapa pesan dari utas utama ke masing-masing dari dua utas yang dibuat. Kami tidak dapat menggunakanstd::promise
danstd::future
untuk ini karena janji / duo masa depan adalah satu tembakan dan tidak dapat digunakan berulang kali.Sumber untuk kelas
Sync_queue
ini berasal dari Bahasa Pemrograman C ++ Stroustrup: Edisi ke-4.Aplikasi sederhana ini menciptakan output berikut.
sumber
Janji itu adalah ujung kabel.
Bayangkan Anda perlu mengambil nilai yang
future
sedang dihitung olehasync
. Namun, Anda tidak ingin itu dihitung dalam utas yang sama, dan Anda bahkan tidak menelurkan utas "sekarang" - mungkin perangkat lunak Anda dirancang untuk memilih utas dari kumpulan, sehingga Anda tidak tahu siapa yang akan melakukan perhitungan che pada akhirnya.Sekarang, apa yang Anda berikan pada utas / kelas / entitas ini (belum diketahui)? Anda tidak lulus
future
, karena ini hasilnya . Anda ingin meneruskan sesuatu yang terhubung kefuture
dan yang mewakili ujung kabel , jadi Anda hanya akan menanyakanfuture
tanpa pengetahuan tentang siapa yang benar-benar akan menghitung / menulis sesuatu.Ini adalah
promise
. Ini adalah pegangan yang terhubung dengan Andafuture
. Jika itufuture
adalah speaker , dan denganget()
Anda mulai mendengarkan sampai beberapa suara keluar, itupromise
adalah mikrofon ; tapi bukan sembarang mikrofon, itu adalah yang mikrofon terhubung dengan kawat tunggal untuk pembicara Anda pegang. Anda mungkin tahu siapa yang ada di ujung tetapi Anda tidak perlu mengetahuinya - Anda hanya memberikannya dan menunggu sampai pihak lain mengatakan sesuatu.sumber
http://www.cplusplus.com/reference/future/promise/
Satu penjelasan kalimat: furture :: get () menunggu promse :: set_value () selamanya.
sumber