Apa itu std :: janji?

384

Saya cukup akrab dengan C ++ 11's std::thread, std::asyncdan std::futurekomponen (misalnya lihat jawaban ini ), yang lurus ke depan.

Namun, saya tidak dapat memahami apa std::promiseitu, 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::promisedibutuhkan dan di mana itu adalah solusi yang paling idiomatis?

Kerrek SB
sumber
2
Berikut ini beberapa kode dengan: en.cppreference.com/w/cpp/thread/future
chris
58
Versi yang sangat, sangat singkat adalah: dari std::promisemana std::futureasalnya. std::futureadalah apa yang memungkinkan Anda untuk mengambil nilai yang telah dijanjikan kepada Anda. Ketika Anda memanggil get()masa depan, ia menunggu sampai pemiliknya std::promisemenentukan nilainya (dengan memanggil set_valuejanji). Jika janji itu dihancurkan sebelum nilai ditentukan, dan Anda kemudian memanggil get()masa depan yang terkait dengan janji itu, Anda akan mendapatkan std::broken_promisepengecualian karena Anda dijanjikan nilai, tetapi tidak mungkin bagi Anda untuk mendapatkannya.
James McNellis
14
Saya merekomendasikan bahwa, jika Anda bisa / mau, lihatlah C ++ Concurrency in Action oleh Anthony Williams
David Rodríguez - dribeas
32
@ GerrekSB std::broken_promiseadalah pengidentifikasi terbaik di perpustakaan standar. Dan tidak ada std::atomic_future.
Cubbi
3
Downvoter, tolong jelaskan keberatan Anda?
Kerrek SB

Jawaban:

182

Dalam kata-kata [futures.state] a std::futureadalah objek kembali asinkron ("objek yang membaca hasil dari keadaan bersama") dan std::promisemerupakan 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::promiseadalah satu jenis penyedia asinkron, std::packaged_taskyang lain, dan detail internal std::asyncyang lain. Masing-masing dapat membuat status bersama dan memberi Andastd::future yang membagikan status itu, dan dapat membuat negara siap.

std::asyncadalah 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 dengan std::packaged_task(atau std::binddan std::promise) dan a std::threadtetapi lebih aman dan lebih mudah digunakanstd::async .

std::promiseadalah 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 diteruskan std::async. Misalnya, Anda mungkin memiliki array beberapa promisedan terkait futuredan memiliki satu utas yang melakukan beberapa perhitungan dan menetapkan hasil pada setiap janji. asynchanya akan memungkinkan Anda untuk mengembalikan satu hasil, untuk mengembalikan beberapa Anda perlu menelepon asyncbeberapa kali, yang mungkin menghabiskan sumber daya.

Jonathan Wakely
sumber
10
Mungkinkah sumber daya limbah? Mungkin salah, jika kode itu tidak bisa diparalelkan.
Puppy
"asynchronous return" dan "membaca hasil dari shared state" kebanyakan ortogonal, yang membuat kalimat pertama agak membingungkan. Apakah Anda bermaksud mengatakan bahwa pembagian negara adalah antara masa depan dan janji? Jika demikian, tolong katakan itu secara eksplisit sejak awal.
einpoklum
@einpoklum mengapa Anda berhenti membaca "objek kembali asinkron" sebelum kata terakhir? Saya mengutip terminologi standar. A futureadalah contoh konkret dari objek kembali asinkron , yang merupakan objek yang membaca hasil yang dikembalikan secara tidak sinkron, melalui keadaan bersama. A promiseadalah 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.
Jonathan Wakely
496

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:

int foo(double, char, bool);

Pertama, kami memiliki template std::future<T>, yang mewakili nilai tipe masa depan T. Nilai dapat diambil melalui fungsi anggota get(), yang secara efektif menyinkronkan program dengan menunggu hasilnya. Atau, dukungan masa depan wait_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:

  1. std::async: Cara paling mudah dan mudah untuk melakukan perhitungan asinkron adalah melalui asynctemplat fungsi, yang segera mengembalikan pencocokan di masa depan:

    auto fut = std::async(foo, 1.5, 'x', false);  // is a std::future<int>

    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:

    auto res = fut.get();  // is an int
  2. 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:

    std::packaged_task<int(double, char, bool)> tsk(foo);
    
    auto fut = tsk.get_future();    // is a std::future<int>

    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:

    std::thread thr(std::move(tsk), 1.5, 'x', false);

    Utas mulai berjalan segera. Kita dapat detachmelakukannya, atau memilikinya joindi akhir ruang lingkup, atau kapan saja (misalnya menggunakan scoped_threadpembungkus Anthony Williams , yang seharusnya ada di perpustakaan standar). Namun, detail penggunaannya std::threadtidak menjadi perhatian kami di sini; pastikan untuk bergabung atau melepaskan thrakhirnya. Yang penting adalah bahwa setiap kali panggilan fungsi selesai, hasil kami siap:

    auto res = fut.get();  // as before
  3. Sekarang kita turun ke level terendah: Bagaimana kita mengimplementasikan tugas yang dikemas? Di sinilah yang std::promisedatang. 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:

    template <typename> class my_task;
    
    template <typename R, typename ...Args>
    class my_task<R(Args...)>
    {
        std::function<R(Args...)> fn;
        std::promise<R> pr;             // the promise of the result
    public:
        template <typename ...Ts>
        explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { }
    
        template <typename ...Ts>
        void operator()(Ts &&... ts)
        {
            pr.set_value(fn(std::forward<Ts>(ts)...));  // fulfill the promise
        }
    
        std::future<R> get_future() { return pr.get_future(); }
    
        // disable copy, default move
    };

    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 dari std::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 ditetapkan set_exception()sebelum masa hidupnya berakhir jika masa depannya ingin dikonsumsi. Janji yang puas dapat mati tanpa konsekuensi, dan get()tersedia di masa depan. Sebuah janji dengan pengecualian akan memunculkan eksepsi yang tersimpan atas panggilan get()di masa depan. Jika janji itu mati tanpa nilai atau pengecualian, menyerukan get()masa depan akan menimbulkan pengecualian "janji rusak".

Berikut ini adalah serangkaian uji untuk menunjukkan berbagai perilaku luar biasa ini. Pertama, harness:

#include <iostream>
#include <future>
#include <exception>
#include <stdexcept>

int test();

int main()
{
    try
    {
        return test();
    }
    catch (std::future_error const & e)
    {
        std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl;
    }
    catch (std::exception const & e)
    {
        std::cout << "Standard exception: " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Unknown exception." << std::endl;
    }
}

Sekarang ke tes.

Kasus 1: Janji tidak aktif

int test()
{
    std::promise<int> pr;
    return 0;
}
// fine, no problems

Kasus 2: Janji aktif, tidak digunakan

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();
    return 0;
}
// fine, no problems; fut.get() would block indefinitely

Kasus 3: Terlalu banyak masa depan

int test()
{
    std::promise<int> pr;
    auto fut1 = pr.get_future();
    auto fut2 = pr.get_future();  //   Error: "Future already retrieved"
    return 0;
}

Kasus 4: Janji yang dipuaskan

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
    }

    return fut.get();
}
// Fine, returns "10".

Kasus 5: Terlalu banyak kepuasan

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
        pr2.set_value(10);  // Error: "Promise already satisfied"
    }

    return fut.get();
}

Pengecualian yang sama dilemparkan jika ada lebih dari satu baik dari set_valueatauset_exception .

Kasus 6: Pengecualian

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo")));
    }

    return fut.get();
}
// throws the runtime_error exception

Kasus 7: Patah janji

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
    }   // Error: "broken promise"

    return fut.get();
}
Kerrek SB
sumber
Anda berkata "... yang secara efektif menyinkronkan program dengan menunggu hasilnya." . Apa maksud "sinkronisasi" di sini? Apa arti seluruh pernyataan itu? Saya tidak dapat memahami ini. Tak satu pun dari arti "sinkronisasi" dari entri kamus ini membantu saya memahami kalimat. Apakah hanya "menunggu" berarti "sinkronisasi"? Apakah setiap menunggu disinkronkan? Saya pikir saya sebagian mengerti apa yang Anda maksud, tapi saya tidak yakin apa yang Anda maksud sebenarnya .
Nawaz
9
Jawaban yang bagus, terima kasih atas bantuan Anda. Mengenai bagian dari std :: async, saya ingat bahwa kami dapat menentukan apakah akan menelurkan utas lain atau bekerja secara sinkron dengan flag (std :: launch :: async, std :: launch :: deferred)
StereoMatching
1
@FelixDombek: Penerusan sempurna dll. std::functionMemiliki banyak konstruktor; tidak ada alasan untuk tidak memaparkannya kepada konsumen my_task.
Kerrek SB
1
@DaveedV .: Terima kasih atas umpan baliknya! Ya, itu adalah ujian kasus 7: Jika Anda menghancurkan janji tanpa menetapkan nilai atau pengecualian, maka memanggil get()masa depan menimbulkan pengecualian. Saya akan mengklarifikasi ini dengan menambahkan "sebelum dihancurkan"; tolong beri tahu saya jika itu cukup jelas.
Kerrek SB
3
Akhirnya got()saya futuregrokking perpustakaan dukungan thread pada promisepenjelasan luar biasa Anda!
bulan cerah
33

Bartosz Milewski menyediakan penulisan yang bagus.

C ++ membagi implementasi futures menjadi satu set blok kecil

std :: janji adalah salah satu dari bagian-bagian ini.

Janji adalah wahana untuk meneruskan nilai kembali (atau pengecualian) dari utas yang menjalankan fungsi ke utas yang diuangkan pada fungsi di masa mendatang.

...

Masa depan adalah objek sinkronisasi yang dibangun di sekitar ujung penerima saluran janji.

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:

promise<int> intPromise;
future<int> intFuture = intPromise.get_future();
std::thread t(asyncFun, std::move(intPromise));
// do some other stuff
int result = intFuture.get(); // may throw MyException
Paul Rubel
sumber
4
Melihat janji di konstruktor utas akhirnya membuat uang receh jatuh. Artikel Bartosz mungkin bukan yang terbesar, tetapi ia menjelaskan bagaimana elemen-elemen tersebut saling berhubungan. Terima kasih.
Kerrek SB
28

Dalam perkiraan kasar Anda dapat mempertimbangkan std::promisesebagai ujung lain dari a std::future(ini salah , tetapi untuk ilustrasi Anda dapat berpikir seolah-olah itu). Konsumen akhir saluran komunikasi akan menggunakan a std::futureuntuk mengkonsumsi datum dari negara bersama, sedangkan utas produsen akan menggunakan a std::promiseuntuk menulis ke negara bersama.

David Rodríguez - dribeas
sumber
12
@ GerrekSB: std::asyncdapat secara konseptual (ini tidak diamanatkan oleh standar) dipahami sebagai fungsi yang menciptakan std::promise, mendorongnya ke dalam kumpulan benang (macam, mungkin kumpulan benang, mungkin utas baru, ...) dan mengembalikan terkait std::futuredengan penelepon. Di sisi klien Anda akan menunggu di std::futuredan utas di ujung yang lain akan menghitung hasilnya dan menyimpannya di std::promise. Catatan: standar mensyaratkan keadaan bersama dan std::futuretetapi tidak adanya a std::promisedalam kasus penggunaan khusus ini.
David Rodríguez - dribeas
6
@ GerrekSB: std::futuretidak akan memanggil joinutas, ia memiliki pointer ke keadaan bersama yang merupakan buffer komunikasi yang sebenarnya. Keadaan bersama memiliki mekanisme sinkronisasi (mungkin std::function+ std::condition_variableuntuk mengunci penelepon hingga std::promiseterpenuhi. Eksekusi utas adalah ortogonal untuk semua ini, dan dalam banyak implementasi Anda mungkin menemukan bahwa std::asynctidak dijalankan oleh utas baru yang kemudian bergabung, tetapi alih-alih oleh kumpulan utas yang masa hidupnya diperpanjang hingga akhir program
David Rodríguez - dribeas
1
@ DavidRodríguez-dribeas: silakan edit informasi dari komentar menjadi jawaban Anda.
Marc Mutz - mmutz
2
@ JonathanWakely: Itu tidak berarti bahwa itu harus dieksekusi di utas baru, hanya bahwa itu harus dijalankan secara asinkron seolah-olah itu dijalankan di utas yang baru dibuat. Keuntungan utama std::asyncadalah 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 .
David Rodríguez - dribeas
1
Utas lokal perlu diinisialisasi ulang, tetapi aturan seolah-olah memungkinkan apa pun (itulah sebabnya saya menuliskan "seolah-olah" dalam huruf miring :)
Jonathan Wakely
11

std::promiseadalah saluran atau jalur untuk informasi yang akan dikembalikan dari fungsi async. std::futureadalah mekanisme sinkronisasi yang membuat pemanggil menunggu sampai nilai kembali yang dibawa dalam std::promisesiap (artinya nilainya diatur di dalam fungsi).

kjp
sumber
8

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:

  1. The tugas (logika dikemas sebagai beberapa objek functor) yang akan RUN 'suatu tempat'.
  2. The aktual pengolahan simpul - thread, proses, dll yang DERET functors seperti ketika mereka disediakan untuk itu. Lihatlah pola desain "Perintah" untuk ide yang bagus tentang bagaimana kumpulan pekerja dasar melakukan ini.
  3. The Hasil pegangan : Seseorang perlu hasil itu, dan kebutuhan objek yang akan GET untuk mereka. Untuk OOP dan alasan lainnya, setiap menunggu atau sinkronisasi harus dilakukan dalam API pegangan ini.

C ++ 11 menyebut hal-hal yang saya bicarakan dalam (1) std::promise, dan hal-hal dalam (3) std::future. std::threadadalah 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).

Zack Yezek
sumber
7

A std::promisedibuat sebagai titik akhir untuk pasangan janji / masa depan dan std::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 a std::promiseke utas yang menggunakan get()a std::futureuntuk menerima data. Pengecualian dihasilkan jikaget() metode masa depan dipanggil lebih dari sekali.

Jika benang dengan std::promisebelum digunakan set_value()untuk memenuhi janjinya maka ketika panggilan thread kedua get()dari std::futureuntuk mengumpulkan janji, benang kedua akan masuk ke keadaan menunggu sampai janji itu dipenuhi oleh thread pertama dengan std::promiseketika menggunakan set_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 menggunakan std::futuredan std::asyncmenulis fungsionalitas coroutine. Lihat diskusi dan contoh di https://stackoverflow.com/a/50753040/1466970 yang memiliki sebagai satu bagian yang membahas penggunaan std::futuredengan co_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::coutakan jelas dan bahwa teks dari beberapa utas tidak akan berbaur.

Bagian pertama dari main()membuat tiga utas tambahan dan menggunakan std::promisedan std::futureuntuk 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 menggunakan std::promisedan std::futureuntuk ini karena janji / duo masa depan adalah satu tembakan dan tidak dapat digunakan berulang kali.

Sumber untuk kelas Sync_queueini berasal dari Bahasa Pemrograman C ++ Stroustrup: Edisi ke-4.

// cpp_threads.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <thread>  // std::thread is defined here
#include <future>  // std::future and std::promise defined here

#include <list>    // std::list which we use to build a message queue on.

static std::atomic<int> kount(1);       // this variable is used to provide an identifier for each thread started.

//------------------------------------------------
// create a simple queue to let us send notifications to some of our threads.
// a future and promise are one shot type of notifications.
// we use Sync_queue<> to have a queue between a producer thread and a consumer thread.
// this code taken from chapter 42 section 42.3.4
//   The C++ Programming Language, 4th Edition by Bjarne Stroustrup
//   copyright 2014 by Pearson Education, Inc.
template<typename Ttype>
class Sync_queue {
public:
    void  put(const Ttype &val);
    void  get(Ttype &val);

private:
    std::mutex mtx;                   // mutex used to synchronize queue access
    std::condition_variable cond;     // used for notifications when things are added to queue
    std::list <Ttype> q;              // list that is used as a message queue
};

template<typename Ttype>
void Sync_queue<Ttype>::put(const Ttype &val) {
    std::lock_guard <std::mutex> lck(mtx);
    q.push_back(val);
    cond.notify_one();
}

template<typename Ttype>
void Sync_queue<Ttype>::get(Ttype &val) {
    std::unique_lock<std::mutex> lck(mtx);
    cond.wait(lck, [this]{return  !q.empty(); });
    val = q.front();
    q.pop_front();
}
//------------------------------------------------


// thread function that starts up and gets its identifier and then
// waits for a promise to be filled by some other thread.
void func(std::promise<int> &jj) {
    int myId = std::atomic_fetch_add(&kount, 1);   // get my identifier
    std::future<int> intFuture(jj.get_future());
    auto ll = intFuture.get();   // wait for the promise attached to the future
    std::cout << "  func " << myId << " future " << ll << std::endl;
}

// function takes a promise from one thread and creates a value to provide as a promise to another thread.
void func2(std::promise<int> &jj, std::promise<int>&pp) {
    int myId = std::atomic_fetch_add(&kount, 1);   // get my identifier
    std::future<int> intFuture(jj.get_future());
    auto ll = intFuture.get();     // wait for the promise attached to the future

    auto promiseValue = ll * 100;   // create the value to provide as promised to the next thread in the chain
    pp.set_value(promiseValue);
    std::cout << "  func2 " << myId << " promised " << promiseValue << " ll was " << ll << std::endl;
}

// thread function that starts up and waits for a series of notifications for work to do.
void func3(Sync_queue<int> &q, int iBegin, int iEnd, int *pInts) {
    int myId = std::atomic_fetch_add(&kount, 1);

    int ll;
    q.get(ll);    // wait on a notification and when we get it, processes it.
    while (ll > 0) {
        std::cout << "  func3 " << myId << " start loop base " << ll << " " << iBegin << " to " << iEnd << std::endl;
        for (int i = iBegin; i < iEnd; i++) {
            pInts[i] = ll + i;
        }
        q.get(ll);  // we finished this job so now wait for the next one.
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::chrono::milliseconds myDur(1000);

    // create our various promise and future objects which we are going to use to synchronise our threads
    // create our three threads which are going to do some simple things.
    std::cout << "MAIN #1 - create our threads." << std::endl;

    // thread T1 is going to wait on a promised int
    std::promise<int> intPromiseT1;
    std::thread t1(func, std::ref(intPromiseT1));

    // thread T2 is going to wait on a promised int and then provide a promised int to thread T3
    std::promise<int> intPromiseT2;
    std::promise<int> intPromiseT3;

    std::thread t2(func2, std::ref(intPromiseT2), std::ref(intPromiseT3));

    // thread T3 is going to wait on a promised int and then provide a promised int to thread Main
    std::promise<int> intPromiseMain;
    std::thread t3(func2, std::ref(intPromiseT3), std::ref(intPromiseMain));

    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2 - provide the value for promise #1" << std::endl;
    intPromiseT1.set_value(22);

    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2.2 - provide the value for promise #2" << std::endl;
    std::this_thread::sleep_for(myDur);
    intPromiseT2.set_value(1001);
    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2.4 - set_value 1001 completed." << std::endl;

    std::future<int> intFutureMain(intPromiseMain.get_future());
    auto t3Promised = intFutureMain.get();
    std::cout << "MAIN #2.3 - intFutureMain.get() from T3. " << t3Promised << std::endl;

    t1.join();
    t2.join();
    t3.join();

    int iArray[100];

    Sync_queue<int> q1;    // notification queue for messages to thread t11
    Sync_queue<int> q2;    // notification queue for messages to thread t12

    std::thread t11(func3, std::ref(q1), 0, 5, iArray);     // start thread t11 with its queue and section of the array
    std::this_thread::sleep_for(myDur);
    std::thread t12(func3, std::ref(q2), 10, 15, iArray);   // start thread t12 with its queue and section of the array
    std::this_thread::sleep_for(myDur);

    // send a series of jobs to our threads by sending notification to each thread's queue.
    for (int i = 0; i < 5; i++) {
        std::cout << "MAIN #11 Loop to do array " << i << std::endl;
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
        q1.put(i + 100);
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
        q2.put(i + 1000);
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
    }

    // close down the job threads so that we can quit.
    q1.put(-1);    // indicate we are done with agreed upon out of range data value
    q2.put(-1);    // indicate we are done with agreed upon out of range data value

    t11.join();
    t12.join();
    return 0;
}

Aplikasi sederhana ini menciptakan output berikut.

MAIN #1 - create our threads.
MAIN #2 - provide the value for promise #1
  func 1 future 22
MAIN #2.2 - provide the value for promise #2
  func2 2 promised 100100 ll was 1001
  func2 3 promised 10010000 ll was 100100
MAIN #2.4 - set_value 1001 completed.
MAIN #2.3 - intFutureMain.get() from T3. 10010000
MAIN #11 Loop to do array 0
  func3 4 start loop base 100 0 to 5
  func3 5 start loop base 1000 10 to 15
MAIN #11 Loop to do array 1
  func3 4 start loop base 101 0 to 5
  func3 5 start loop base 1001 10 to 15
MAIN #11 Loop to do array 2
  func3 4 start loop base 102 0 to 5
  func3 5 start loop base 1002 10 to 15
MAIN #11 Loop to do array 3
  func3 4 start loop base 103 0 to 5
  func3 5 start loop base 1003 10 to 15
MAIN #11 Loop to do array 4
  func3 4 start loop base 104 0 to 5
  func3 5 start loop base 1004 10 to 15
Richard Chambers
sumber
1

Janji itu adalah ujung kabel.

Bayangkan Anda perlu mengambil nilai yang futuresedang dihitung oleh async. 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 ke futuredan yang mewakili ujung kabel , jadi Anda hanya akan menanyakan futuretanpa pengetahuan tentang siapa yang benar-benar akan menghitung / menulis sesuatu.

Ini adalah promise. Ini adalah pegangan yang terhubung dengan Anda future. Jika itu futureadalah speaker , dan dengan get()Anda mulai mendengarkan sampai beberapa suara keluar, itu promiseadalah 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.

Narcolessico
sumber
0

http://www.cplusplus.com/reference/future/promise/

Satu penjelasan kalimat: furture :: get () menunggu promse :: set_value () selamanya.

void print_int(std::future<int>& fut) {
    int x = fut.get(); // future would wait prom.set_value forever
    std::cout << "value: " << x << '\n';
}

int main()
{
    std::promise<int> prom;                      // create promise

    std::future<int> fut = prom.get_future();    // engagement with future

    std::thread th1(print_int, std::ref(fut));  // send future to new thread

    prom.set_value(10);                         // fulfill promise
                                                 // (synchronizes with getting the future)
    th1.join();
    return 0;
}
Zhang
sumber