Std :: promise nedir?


384

Ben C ++ 11 yılların ile oldukça tanıdık std::thread, std::asyncve std::futurebileşenler (örneğin bkz bu cevabı ) düz ileri.

Ancak, ne std::promiseolduğunu, ne yaptığını ve hangi durumlarda en iyi şekilde kullanıldığını tam olarak anlayamıyorum. Standart belgenin kendisi, sınıf özetinin ötesinde bir sürü bilgi içermez ve ikisi de sadece :: thread içermez .

Birisi, a'nın std::promisegerekli olduğu ve en deyimsel çözümün nerede olduğu konusunda kısa ve özlü bir örnek verebilir mi?


2
Burada bununla ilgili bazı kodlar var: en.cppreference.com/w/cpp/thread/future
chris

58
Gerçekten çok kısa versiyonu: std::promisenereden std::futuregeliyor. std::futuresize vaat edilen bir değeri almanızı sağlayan şeydir . Bir get()geleceği çağırdığınızda std::promise, değeri ayarladığı sahibinin sahibine kadar bekler ( set_valuevaadi arayarak ). Bir değer ayarlanmadan önce söz imha edilirse ve daha sonra get()bu söz ile ilişkili bir geleceğe çağrı std::broken_promiseyaparsanız, bir değer vaat ettiğiniz için bir istisna alırsınız , ancak bir değer almanız imkansızdır.
James McNellis

14
Ben eğer yapabilirsen istiyorum /, tavsiye bakmak Eylem C ++ eşzamanlılık tarafından Anthony Williams
dribeas - David Rodríguez

32
@KerrekSB std::broken_promise, standart kütüphanedeki en iyi adlandırılmış tanımlayıcıdır. Ve yok std::atomic_future.
Cubbi

3
Downvoter, itirazınızı açıklamak ister misiniz?
Kerrek SB

Yanıtlar:


182

[Futures.state] ifadesinde a std::future, eşzamansız bir dönüş nesnesidir ("paylaşılan bir durumdan sonuçları okuyan bir nesne") ve a std::promise, eşzamansız bir sağlayıcıdır ("paylaşılan bir duruma sonuç sağlayan bir nesne") yani söz, sonucu belirlediğiniz şeydir , böylece ilişkili gelecekten elde edebilirsiniz .

Zaman uyumsuz sağlayıcı, başlangıçta geleceğin başvurduğu paylaşılan durumu yaratan şeydir. std::promisebir tür eşzamansız sağlayıcı, std::packaged_taskdiğeri ve iç detayı std::asyncbaşka. Bunların her biri paylaşılan bir durum oluşturabilir ve size std::futurebu durumu paylaşan bir durum verebilir ve durumu hazırlayabilir.

std::asyncsize eşzamansız bir sonuç nesnesi sağlayan ve dahili olarak eşzamansız sağlayıcı oluşturma ve görev tamamlandığında paylaşılan durumu hazır hale getirme konusunda üst düzey bir yardımcı programdır. Bir std::packaged_task(veya std::binda std::promise) ve a ile taklit edebilirsiniz , std::threadancak daha güvenli ve kullanımı daha kolaydır std::async.

std::promise, geleceğe eşzamansız bir sonuç iletmek istediğinizde biraz daha düşük düzeydedir, ancak sonucu hazırlayan kod, iletilmeye uygun tek bir işlevle tamamlanamaz std::async. Örneğin, birkaç promises ve ilişkili futures'den oluşan bir diziniz olabilir ve birkaç hesaplama yapan ve her vaatte bir sonuç ayarlayan tek bir iş parçacığına sahip olabilirsiniz. asyncyalnızca tek bir sonuç döndürmenize izin verir, birkaç döndürmek için asyncbirkaç kez aramanız gerekir , bu da kaynakları boşa harcayabilir.


10
Atık kaynakları olabilir mi? Bu kod paralelleştirilemezse yanlış olabilir.
Köpek yavrusu

"eşzamansız dönüş" ve "paylaşılan durumdan kaynaklanan okumalar" çoğunlukla diktir, bu da ilk cümleyi biraz kafa karıştırıcı yapar. Devletin paylaşımının gelecekle vaat arasında olduğunu mu söylemek istiyorsun? Eğer öyleyse, lütfen bunu en baştan açıkça söyleyin.
einpoklum

@einpoklum neden son sözcükten önce "eşzamansız dönüş nesnesi" ni okumayı bıraktınız? Standardın terminolojisinden alıntı yapıyorum. A future, paylaşılan durum aracılığıyla eşzamansız olarak döndürülen bir sonucu okuyan bir nesne olan eşzamansız bir dönüş nesnesinin somut bir örneğidir . A promise, eşzamansız olarak okunabilen, paylaşılan duruma bir değer yazan bir nesne olan eşzamansız bir sağlayıcının somut bir örneğidir . Ne yazdığımı kastetmiştim.
Jonathan Wakely

496

Durumu şimdi biraz daha iyi anlıyorum (buradaki cevaplar nedeniyle küçük bir miktar yok!)


C ++ 11'de birbiriyle ilişkili olmakla birlikte iki farklı kavram vardır: Asenkron hesaplama (başka bir yerde adlandırılan bir işlev) ve eşzamanlı yürütme (bir iş parçacığı , aynı anda çalışan bir şey). İkisi biraz dik kavramlardır. Bir iş parçacığı yürütme bağlamı iken, eşzamansız hesaplama işlev çağrısının sadece farklı bir lezzetidir. Konular kendi başlarına faydalıdır, ancak bu tartışmanın amacı için, bunları bir uygulama detayı olarak ele alacağım.


Eşzamansız hesaplama için bir soyutlama hiyerarşisi vardır. Örneğin, bazı argümanlar alan bir fonksiyonumuz olduğunu varsayalım:

int foo(double, char, bool);

Öncelikle, std::future<T>gelecekteki bir tür değerini temsil eden şablona sahibiz T. Değer get(), sonucu bekleyerek programı etkili bir şekilde senkronize eden üye fonksiyonu aracılığıyla alınabilir . Alternatif olarak, wait_for()sonucun zaten mevcut olup olmadığını araştırmak için kullanılabilecek bir gelecek destekleri . Vadeli işlemler, sıradan getiri türleri için eşzamansız bırakma ikamesi olarak düşünülmelidir. Örnek fonksiyonumuz için a std::future<int>.

Şimdi, hiyerarşiye, en yüksekten en düşüğe doğru:

  1. std::async: Eşzamansız bir hesaplama yapmanın en uygun ve basit yolu async, eşleşen geleceği hemen döndüren işlev şablonu:

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

    Detaylar üzerinde çok az kontrole sahibiz. Özellikle, işlevin aynı anda, seri olarak get()veya başka bir kara büyü tarafından yürütülüp yürütülmediğini bile bilmiyoruz . Bununla birlikte, sonuç gerektiğinde kolayca elde edilir:

    auto res = fut.get();  // is an int
  2. Şimdi böyle bir şeyi nasıl uygulayacağımızı düşünebiliriz async, ancak kontrol ettiğimiz bir şekilde . Örneğin, işlevin ayrı bir iş parçacığında yürütülmesi konusunda ısrar edebiliriz. std::threadSınıf aracılığıyla ayrı bir iş parçacığı sağlayabileceğimizi zaten biliyoruz .

    Soyutlama sonraki düşük seviye tam olarak bunu yapıyor: std::packaged_task. Bu, bir işlevi saran ve işlevlerin dönüş değeri için bir gelecek sağlayan bir şablondur, ancak nesnenin kendisi çağrılabilir ve çağırma kullanıcının isteğine bağlıdır. Bunu şu şekilde ayarlayabiliriz:

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

    Görevi çağırdığımızda ve çağrı tamamlandığında gelecek hazır olur. Bu ayrı bir iplik için ideal bir iştir. Görevi iş parçacığına taşıdığımızdan emin olmalıyız :

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

    İş parçacığı hemen çalışmaya başlar. Onu ya detachda joinkapsamın sonunda ya da ne zaman alabiliriz (örneğin scoped_thread, standart kütüphanede olması gereken Anthony Williams sarıcısını kullanarak ). Bununla birlikte, kullanma ayrıntıları std::threadburada bizi ilgilendirmiyor; threninde sonunda katıldığınızdan veya ayırdığınızdan emin olun . Önemli olan, işlev çağrısı bittiğinde, sonucumuzun hazır olmasıdır:

    auto res = fut.get();  // as before
  3. Şimdi en düşük seviyeye iniyoruz: Paketlenmiş görevi nasıl uygularız ? İşte bu noktada std::promisedevreye giriyor. Söz, bir gelecekle iletişim kurmak için yapı taşıdır. Temel adımlar şunlardır:

    • Çağıran evre bir söz verir.

    • Çağıran evre sözden bir gelecek elde eder.

    • Vaat, işlev bağımsız değişkenleriyle birlikte ayrı bir iş parçacığına taşınır.

    • Yeni iş parçacığı işlevi yürütür ve vaat yerine getirir.

    • Orijinal iş parçacığı sonucu alır.

    Örnek olarak, işte kendi "paketlenmiş görevimiz":

    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
    };

    Bu şablonun kullanımı aslında ile aynıdır std::packaged_task. Tüm görevin taşınmasının sözün taşınmasını sağladığını unutmayın. Daha geçici durumlarda, bir vaat nesnesini açıkça yeni iş parçacığına taşıyabilir ve iş parçacığı işlevinin bir işlev bağımsız değişkeni yapabilir, ancak yukarıdaki gibi bir görev sargısı daha esnek ve daha az müdahaleci bir çözüm gibi görünebilir.


İstisnalar yapmak

Vaatler istisnalarla yakından ilgilidir. Bir sözün tek başına arayüzü, durumunu tamamen aktarmak için yeterli değildir, bu nedenle bir söz üzerinde bir işlem mantıklı olmadığında istisnalar atılır. Tüm istisnalar türden std::future_errortüretilmiştir std::logic_error. İlk olarak, bazı kısıtlamaların açıklaması:

  • Varsayılan olarak oluşturulmuş bir vaat etkin değildir. Etkin olmayan vaatler sonuç vermeden ölebilir.

  • Bir gelecek, yoluyla bir gelecek elde edildiğinde aktif olur get_future(). Ancak, sadece bir gelecek elde edilebilir!

  • Bir sözün, eğer geleceği tüketilecekse, ömrü sona ermeden önce ya karşılanması set_value()veya bir istisna oluşturması set_exception()gerekir. Tatmin edici bir söz sonuç vermeden ölebilir get()ve gelecekte kullanılabilir hale gelir. İstisnai bir vaat get(), geleceğin çağrısı üzerine depolanan istisnayı artıracaktır . Söz ne değer ne de istisna ile ölürse get(), geleceğe çağrı yapmak "kırık söz" istisnasını doğuracaktır.

İşte bu çeşitli istisnai davranışları göstermek için küçük bir test serisi. İlk olarak, koşum takımı:

#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;
    }
}

Şimdi testlere geçelim.

Durum 1: Etkin olmayan söz

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

Durum 2: Aktif vaat, kullanılmayan

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

Durum 3: Çok fazla gelecek

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

Durum 4: Memnun vaat

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".

Durum 5: Çok fazla memnuniyet

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();
}

Birden fazla varsa aynı durum atılır ya ait set_valueveya set_exception.

Durum 6: İstisna

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

Durum 7: Bozuk söz

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

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

    return fut.get();
}

"... sonucu bekleyerek programı etkili bir şekilde senkronize eden" dediniz . . "Senkronizasyonlar" burada ne anlama geliyor? İfadenin tamamı ne anlama geliyor? Bunu anlayamıyorum. Bu sözlük girişindeki "senkronize et" anlamının hiçbiri cümleyi anlamama yardımcı olmaz. Sadece "beklemek", "senkronizasyon" anlamına mı gelir? Her bekleme senkronize mi? Sanırım ne demek istediğini kısmen anlıyorum, ama aslında ne demek istediğinden emin değilim .
Nawaz

9
Güzel cevap, yardımın için teşekkürler. Std :: async parçası hakkında, başka bir iş parçacığı oluşturacağını veya bayrakla eşzamanlı çalışacağını belirleyebileceğimizi hatırlıyorum (std :: launch :: async, std :: launch :: ertelenmiş)
StereoMatching

1
@FelixDombek: Mükemmel yönlendirme vb. std::functionBirçok kurucuya sahiptir; bunları tüketiciye maruz bırakmamak için bir sebep yok my_task.
Kerrek SB

1
@DaveedV .: Geri bildiriminiz için teşekkür ederiz! Evet, bu test örneği 7: Sözü değer veya istisna ayarlamadan yok ederseniz get(), geleceğe çağrı yapmak bir istisna oluşturur. Bunu "yok edilmeden önce" ekleyerek açıklığa kavuşturacağım; lütfen bunun yeterince açık olup olmadığını bana bildirin.
Kerrek SB

3
Sonunda got()benim şaşırtıcı açıklama futureüzerinde iplik destek kütüphanesi grokking promise!
güneşli ay

33

Bartosz Milewski iyi bir yazım sağlar.

C ++, vadeli işlemlerin uygulanmasını küçük bloklara ayırır

std :: promise bu parçalardan biridir.

Bir vaat, bir işlevi yerine getiren evreden dönüş değerini (ya da bir istisnayı), gelecekteki işlevde para kazandıran evreye geçirmek için kullanılan bir araçtır.

...

Bir gelecek, vaat kanalının alıcı ucu etrafında inşa edilen senkronizasyon nesnesidir.

Dolayısıyla, bir geleceği kullanmak istiyorsanız, eşzamansız işlemenin sonucunu elde etmek için kullandığınız bir söz ile sonuçlanırsınız.

Sayfadan bir örnek:

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

4
İpliğin yapıcısındaki vaatleri görmek nihayet kuruşu düşürdü. Bartosz'un makalesi belki de en iyisi değil, ama elementlerin nasıl birbirine bağlandığını açıklıyor. Teşekkürler.
Kerrek SB

28

Kaba bir yaklaşımda, a'nın std::promisediğer ucu olarak düşünebilirsiniz std::future(bu yanlıştır , ancak gösterim için sanki öyleymiş gibi düşünebilirsiniz). İletişim kanalının tüketici sonu std::future, paylaşılan durumdan verileri kullanmak için a, üretici iş parçacığı std::promiseise paylaşılan duruma yazmak için a kullanır.


12
@KerrekSB: std::asynckavramsal olarak (bu standart tarafından zorunlu değildir) std::promise, bir iş parçacığı havuzuna iten bir işlev olarak anlaşılabilir (bir çeşit havuz, bir iş parçacığı havuzu olabilir, yeni bir iş parçacığı olabilir, ...) ve döndürür std::futureArayanla ilişkili . İstemci tarafında beklersiniz std::futureve diğer uçtaki bir iş parçacığı sonucu hesaplar ve içinde saklar std::promise. Not: Standart gerektirir paylaşılan devlet ve std::futureancak bir varlığı std::promisebu özel kullanım durumunda.
David Rodríguez - dribeas

6
@KerrekSB: iş parçacığını std::futureçağırmaz , gerçek iletişim arabelleği olan paylaşılan durumajoin bir işaretçi vardır . Paylaşılan durum bir senkronizasyon mekanizması (muhtemelen vardır + dek arayanı kilitlemek için yerine getirilir dişin yürütme tüm bu ortogonaldir. Ve birçok uygulamalarda bunu bulabilir sonra birleştirilir yeni iş parçacığı tarafından yürütülen değildir, ancak bunun yerine, ömrü programın sonuna kadar uzanan bir iş parçacığı havuzu ilestd::functionstd::condition_variablestd::promisestd::async
David Rodríguez - dribeas

1
@ DavidRodríguez-dribeas: lütfen yorumlardaki bilgileri cevabınıza düzenleyin.
Marc Mutz - mmutz

2
@JonathanWakely: Bu, yeni bir iş parçacığında yürütülmesi gerektiği anlamına gelmez, yalnızca yeni oluşturulan bir iş parçacığında çalıştırıldığı gibi eşzamansız olarak yürütülmesi gerektiği anlamına gelir. Bunun en büyük avantajı std::async, çalışma zamanı kitaplığının oluşturulacak iş parçacığı sayısı ile ilgili olarak sizin için doğru kararları verebilmesidir ve çoğu durumda iş parçacığı havuzlarını kullanan çalışma zamanlarını beklerim. Şu anda VS2012 kaputun altında bir iş parçacığı havuzu kullanıyor ve as-if kuralını ihlal etmiyor . Bu özel if için yerine getirilmesi gereken çok az garanti olduğunu unutmayın .
David Rodríguez - dribeas

1
İş parçacığı yerlilerinin yeniden başlatılması gerekiyor, ancak as-if kuralı herhangi bir şeye izin veriyor (bu yüzden italik olarak "sanki" koydum :)
Jonathan Wakely

11

std::promise, zaman uyumsuz işlevinden döndürülecek bilgi için kanal veya yoldur. std::futureSenkronizasyon mekanizması, taşınan dönüş değeri std::promisehazır olana kadar arayanı bekletir (yani değeri fonksiyonun içinde ayarlanır).


8

Asenkron işlemede gerçekten 3 çekirdek varlık vardır. C ++ 11 şu anda 2 tanesine odaklanıyor.

Bazı mantığı eşzamansız olarak çalıştırmak için ihtiyacınız olan temel şeyler şunlardır:

  1. Görev 'bir yere' RUN (bazı funktoru nesne olarak paketlenmiş mantık).
  2. Gerçek işlem düğüm - bir iplik, bir işlem olup, vb bunun için temin edildiğinde bu functors KEMİĞİDİR. Temel bir işçi iş parçacığı havuzunun bunu nasıl yaptığına dair iyi bir fikir için "Komut" tasarım modeline bakın.
  3. Sonuç kolu : Biri şu sonuca ihtiyacı var ve onlar için GET edecek bir nesneyi ihtiyacı var. OOP ve diğer nedenlerle, bu tanıtıcı API'larında herhangi bir bekleme veya senkronizasyon yapılmalıdır.

C ++ 11, bahsettiğim şeyleri (1) std::promiseve (3) 'de çağırır std::future. std::thread(2) için halka açık olan tek şeydir. Bu talihsiz bir durum çünkü gerçek programların iş parçacığı ve bellek kaynaklarını yönetmesi gerekiyor ve çoğu, görevlerin her küçük görev için bir iş parçacığı oluşturmak ve yok etmek yerine iş parçacığı havuzlarında çalışmasını isteyecek (neredeyse her zaman kendi başına gereksiz performans vuruşlarına neden oluyor ve kolayca kaynak oluşturabiliyor) daha da kötü olan açlık).

Herb Sutter ve C ++ 11 beyin güvenine sahip diğerlerine göre, std::executorJava'da olduğu gibi, bunu eklemek için belirsiz planlar vardır - iş parçacığı havuzları ve mantıksal olarak benzer kurulumlar için temel olacaktır (2). Belki C ++ 2014'te göreceğiz, ama bahse girerim C ++ 17'ye daha çok benziyor (ve bunlar için standardı seçtiyse Tanrı bize yardım ediyor).


7

A std::promise, bir söz / gelecek çifti için bir bitiş noktası olarak oluşturulur ve std::future( get_future()yöntemi kullanarak std :: promise'tan yaratılır ) diğer bitiş noktasıdır. Bu, bir iş parçacığı bir mesaj aracılığıyla başka bir iş parçacığına veri sağladığı için, iki iş parçacığının senkronize edilmesi için bir yol sağlamanın basit, tek adımlık bir yöntemidir.

Bir iş parçacığı veri sağlama vaadi oluştururken diğer iş parçacığı gelecekte vaat toplar gibi düşünebilirsiniz. Bu mekanizma yalnızca bir kez kullanılabilir.

Söz / gelecek mekanizması kullanır parçacığından, sadece bir yönde olan set_value()bir yöntemi std::promisekullanan konuya get()a std::futureveri almak için. Bir get()geleceğin yöntemi birden fazla çağrılırsa bir istisna oluşturulur .

İle iplik halinde std::promisekullanmamış set_value()ikinci iplik çağrıları zaman sonra sözünü yerine getirmesini get()ve std::futuresözünü toplamak için söz ile ilk iplik tarafından teslim edilene kadar, ikinci iplik bir bekleme durumuna geçer std::promiseo kullandığında set_value()yöntemi veri göndermek için.

Önerilen Teknik Şartname N4663 Programlama Dilleri - C ++ Coroutines Uzantıları ve Visual Studio 2017 C ++ derleyici desteği ile birlikte co_await, cohoutine işlevselliğini kullanmak std::futureve std::asyncyazmak da mümkündür . Tartışma ve örneğe bakın https://stackoverflow.com/a/50753040/1466970 kullanımını açıklamaktadır bir bölümü olarak var std::futureolan co_await.

Aşağıdaki örnek kod, basit bir Visual Studio 2013 Windows konsol uygulaması, C ++ 11 eşzamanlılık sınıflarının / şablonlarının ve diğer işlevlerin birkaçının kullanıldığını gösterir. İyi çalışan bir söz / gelecek kullanımı, bazı görevleri yerine getirecek ve durduracak özerk iş parçacıkları ve daha eşzamanlı davranışın gerekli olduğu ve çoklu bildirimlere duyulan ihtiyaç nedeniyle söz / gelecek çifti çalışmaz.

Bu örnekle ilgili bir not, çeşitli yerlere eklenen gecikmelerdir. Bu gecikmeler yalnızca konsola yazdırılan çeşitli iletilerin std::coutnet olmasını ve birkaç iş parçacığından gelen metnin birbirine karışmamasını sağlamak için eklenmiştir .

İlk bölümü main()üç ek iş parçacığı oluşturmak ve iş parçacıkları arasında veri göndermek için std::promiseve std::futurekullanın. İlginç bir nokta, ana iş parçacığının, ana iş parçacığından veri bekleyecek, bir şey yapacak ve daha sonra üçüncü iş parçasına (T3) bir iş parçacığı başlattığı ve daha sonra bir şey yapacak ve verileri geri gönderecek olmasıdır. ana iplik.

Ana iş parçasından main()oluşturulan iki iş parçacığının her birine birden çok iletiye izin vermek için iki iş parçacığı ve bir sıra kümesi oluşturur. Bunu kullanamayız std::promiseve std::futurebunun için söz / gelecek ikilisi tek bir atıştır ve tekrar tekrar kullanılamaz.

Sınıfın kaynağı Sync_queueStroustrup'un The C ++ Programlama Dili: 4. Basımından kaynaklanmaktadır.

// 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;
}

Bu basit uygulama aşağıdaki çıktıyı oluşturur.

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

1

Söz telin diğer ucudur.

futureTarafından hesaplanan bir varlığın değerini almanız gerektiğini düşünün async. Ancak, aynı iş parçacığında hesaplanmasını istemezsiniz ve "şimdi" bir iş parçacığı bile oluşturmazsınız - belki de yazılımınız havuzdan bir iş parçacığı almak üzere tasarlanmıştır, bu yüzden kimin olacağını bilmiyorsunuz sonunda che hesaplaması yapar.

Şimdi, bu (henüz bilinmeyen) iş parçacığına / sınıfa / varlığa ne aktarıyorsunuz? Siz geçemezsiniz future, çünkü sonuç budur . Sen bir şeyi geçirmek istediğiniz bağlı için future, ve onu temsil telin diğer ucunu sadece sorgular böylece futurekim olacak aslında hesaplama / yazma şey hakkında hiçbir bilgiye sahip.

Bu promise. Bu bir sap bağlı future. Eğer futurebir hoparlörse ve get()sizinle birlikte bir miktar ses çıkana kadar dinlemeye başlıyorsanız, bu promisebir mikrofondur ; ama sadece herhangi bir mikrofon, öyle elinizdeki hoparlöre tek tel ile bağlanmış mikrofon. Diğer uçta kimin olduğunu biliyor olabilirsiniz ama bunu bilmenize gerek yok - sadece verin ve karşı taraf bir şey söyleyene kadar bekleyin.


0

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

Bir cümle açıklaması: furture :: get () sonsuza kadar bekler :: set_value ().

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;
}
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.