Std :: lời hứa là gì?


384

Tôi khá quen thuộc với C ++ 11 của std::thread, std::asyncstd::futurecác thành phần (ví dụ như nhìn thấy câu trả lời này ), đó là thẳng về phía trước.

Tuy nhiên, tôi không thể nắm bắt được nó std::promiselà gì, nó làm gì và trong tình huống nào nó được sử dụng tốt nhất. Bản thân tài liệu tiêu chuẩn không chứa nhiều thông tin vượt quá tóm tắt lớp và cũng không chỉ :: luồng .

Ai đó có thể xin vui lòng cho một ví dụ ngắn gọn, súc tích về một tình huống std::promisecần thiết và đó là giải pháp thành ngữ nhất?


2
Đây là một số mã với nó trong: en.cppreference.com/w/cpp/thread/future
chris

58
Các phiên bản thực sự, thực sự ngắn gọn là: std::promiselà nơi std::futures đến từ. std::futurelà những gì cho phép bạn lấy lại một giá trị đã được hứa với bạn. Khi bạn gọi get()về một tương lai, nó sẽ đợi cho đến khi chủ sở hữu của std::promisenó đặt giá trị (bằng cách gọi set_valuetheo lời hứa). Nếu lời hứa bị hủy trước khi giá trị được đặt và sau đó bạn gọi get()một tương lai liên quan đến lời hứa đó, bạn sẽ có một std::broken_promisengoại lệ vì bạn đã hứa một giá trị, nhưng bạn không thể có được một giá trị.
James McNellis

14
Tôi khuyên bạn, nếu bạn có thể / muốn, hãy xem C ++ Đồng thời trong hành động của Anthony Williams
David Rodríguez - dribeas

32
@KerrekSB std::broken_promiselà định danh được đặt tên tốt nhất trong thư viện chuẩn. Và không có std::atomic_future.
Cubbi

3
Downvoter, quan tâm để giải thích phản đối của bạn?
Kerrek SB

Câu trả lời:


182

Theo cách nói của [futures.state] a std::futurelà một đối tượng trả về không đồng bộ ("một đối tượng đọc kết quả từ trạng thái chia sẻ") và a std::promisenhà cung cấp không đồng bộ ("một đối tượng cung cấp kết quả cho trạng thái chia sẻ") tức là một lời hứa là điều mà bạn đặt ra một kết quả, để bạn có thể nhận được nó từ tương lai liên kết.

Nhà cung cấp không đồng bộ là những gì ban đầu tạo ra trạng thái chia sẻ mà tương lai đề cập đến. std::promiselà một loại nhà cung cấp không đồng bộ, std::packaged_tasklà một loại khác và chi tiết bên trong std::asynclà một loại khác. Mỗi người trong số họ có thể tạo trạng thái chia sẻ và cung cấp cho bạn trạng thái chia sẻ trạng thái std::futuređó và có thể làm cho trạng thái sẵn sàng.

std::asynclà một tiện ích tiện lợi cấp cao hơn cung cấp cho bạn một đối tượng kết quả không đồng bộ và bên trong chăm sóc việc tạo nhà cung cấp không đồng bộ và làm cho trạng thái chia sẻ sẵn sàng khi tác vụ hoàn thành. Bạn có thể mô phỏng nó bằng std::packaged_task(hoặc std::bindvà a std::promise) và std::threadnhưng nó an toàn và dễ sử dụng hơn std::async.

std::promiseở mức thấp hơn một chút, khi bạn muốn chuyển một kết quả không đồng bộ cho tương lai, nhưng mã làm cho kết quả sẵn sàng không thể được gói trong một hàm duy nhất phù hợp để chuyển đến std::async. Ví dụ: bạn có thể có một mảng gồm một vài promises và các futures được liên kết và có một luồng duy nhất thực hiện một vài phép tính và đặt kết quả cho mỗi lời hứa. asyncsẽ chỉ cho phép bạn trả về một kết quả duy nhất, để trả về một số bạn sẽ cần gọi asyncnhiều lần, điều này có thể gây lãng phí tài nguyên.


10
Có thể lãng phí tài nguyên? Có thể không chính xác, nếu mã đó không thể được song song.
Cún con

"Trả lại không đồng bộ" và "kết quả đọc từ trạng thái chia sẻ" chủ yếu là trực giao, điều này làm cho câu đầu tiên hơi khó hiểu. Bạn có muốn nói rằng sự chia sẻ của nhà nước là giữa tương lai và lời hứa? Nếu vậy, xin vui lòng nói rõ ràng ngay từ đầu.
einpoklum

@einpoklum tại sao bạn lại ngừng đọc "đối tượng trả lại không đồng bộ" trước từ cuối cùng? Tôi đang trích dẫn thuật ngữ của tiêu chuẩn. A futurelà một ví dụ cụ thể về đối tượng trả lại không đồng bộ , là đối tượng đọc kết quả được trả về không đồng bộ, thông qua trạng thái chia sẻ. A promiselà một ví dụ cụ thể của nhà cung cấp không đồng bộ , là đối tượng ghi giá trị vào trạng thái chia sẻ, có thể được đọc không đồng bộ. Tôi có nghĩa là những gì tôi đã viết.
Jonathan Wakely

496

Tôi hiểu tình hình tốt hơn một chút bây giờ (với số lượng không nhỏ do các câu trả lời ở đây!), Vì vậy tôi nghĩ rằng tôi đã thêm một chút viết lên của riêng tôi.


Có hai khái niệm riêng biệt, mặc dù có liên quan, trong C ++ 11: Tính toán không đồng bộ (một hàm được gọi ở một nơi khác) và thực thi đồng thời (một luồng , một cái gì đó hoạt động đồng thời). Hai là một số khái niệm trực giao. Tính toán không đồng bộ chỉ là một hương vị khác nhau của lệnh gọi hàm, trong khi một luồng là bối cảnh thực thi. Chủ đề là hữu ích theo cách riêng của họ, nhưng với mục đích của cuộc thảo luận này, tôi sẽ coi chúng như một chi tiết thực hiện.


Có một hệ thống trừu tượng cho tính toán không đồng bộ. Ví dụ: vì giả sử chúng ta có một hàm có một số đối số:

int foo(double, char, bool);

Trước hết, chúng ta có mẫu std::future<T>, đại diện cho một giá trị tương lai của loại T. Giá trị có thể được lấy thông qua chức năng thành viên get(), giúp đồng bộ hóa chương trình một cách hiệu quả bằng cách chờ kết quả. Ngoài ra, một hỗ trợ trong tương lai wait_for(), có thể được sử dụng để thăm dò xem kết quả đã có sẵn hay chưa. Tương lai nên được coi là sự thay thế thả xuống không đồng bộ cho các loại lợi nhuận thông thường. Đối với chức năng ví dụ của chúng tôi, chúng tôi mong đợi a std::future<int>.

Bây giờ, trên hệ thống phân cấp, từ cấp cao nhất đến cấp thấp nhất:

  1. std::async: Cách thuận tiện và đơn giản nhất để thực hiện tính toán không đồng bộ là thông qua asyncmẫu hàm, trả về tương lai phù hợp ngay lập tức:

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

    Chúng tôi có rất ít quyền kiểm soát các chi tiết. Cụ thể, chúng ta thậm chí không biết liệu chức năng này có được thực hiện đồng thời, theo kiểu ser seri get()hay bởi một số phép thuật đen khác. Tuy nhiên, kết quả dễ dàng thu được khi cần:

    auto res = fut.get();  // is an int
  2. Bây giờ chúng ta có thể xem xét làm thế nào để thực hiện một cái gì đó như thế nào async, nhưng theo cách mà chúng ta kiểm soát. Ví dụ, chúng tôi có thể nhấn mạnh rằng hàm được thực thi trong một luồng riêng biệt. Chúng ta đã biết rằng chúng ta có thể cung cấp một luồng riêng biệt bằng phương tiện của std::threadlớp.

    Mức độ trừu tượng thấp hơn tiếp theo thực hiện chính xác điều đó : std::packaged_task. Đây là một mẫu bao bọc một hàm và cung cấp một tương lai cho giá trị trả về của các hàm, nhưng bản thân đối tượng có thể gọi được và việc gọi nó theo ý của người dùng. Chúng ta có thể thiết lập nó như thế này:

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

    Tương lai sẽ sẵn sàng khi chúng ta gọi nhiệm vụ và cuộc gọi hoàn thành. Đây là công việc lý tưởng cho một chủ đề riêng biệt. Chúng ta chỉ cần đảm bảo di chuyển tác vụ vào luồng:

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

    Các chủ đề bắt đầu chạy ngay lập tức. Chúng ta có thể detachhoặc có joinnó ở cuối phạm vi hoặc bất cứ khi nào (ví dụ: sử dụng scoped_threadtrình bao bọc của Anthony Williams , thứ thực sự cần có trong thư viện chuẩn). Tuy nhiên, các chi tiết sử dụng std::threadkhông liên quan đến chúng tôi ở đây; chỉ cần chắc chắn để tham gia hoặc tách ra thrcuối cùng. Vấn đề là bất cứ khi nào chức năng gọi kết thúc, kết quả của chúng tôi đã sẵn sàng:

    auto res = fut.get();  // as before
  3. Bây giờ chúng tôi xuống mức thấp nhất: Làm thế nào chúng tôi sẽ thực hiện nhiệm vụ đóng gói? Đây là nơi std::promisexuất hiện. Lời hứa là khối xây dựng để liên lạc với một tương lai. Các bước chính là:

    • Các chủ đề cuộc gọi làm cho một lời hứa.

    • Chuỗi cuộc gọi có được một tương lai từ lời hứa.

    • Lời hứa, cùng với các đối số chức năng, được chuyển thành một luồng riêng biệt.

    • Các chủ đề mới thực hiện chức năng và thực hiện lời hứa.

    • Các chủ đề ban đầu lấy kết quả.

    Ví dụ, đây là "nhiệm vụ đóng gói" rất riêng của chúng tôi:

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

    Việc sử dụng mẫu này về cơ bản là giống như của std::packaged_task. Lưu ý rằng việc di chuyển toàn bộ nhiệm vụ sẽ chuyển lời hứa. Trong các tình huống đặc biệt hơn, người ta cũng có thể chuyển đối tượng lời hứa một cách rõ ràng vào luồng mới và biến nó thành đối số chức năng của hàm luồng, nhưng trình bao bọc nhiệm vụ như ở trên có vẻ như là một giải pháp linh hoạt hơn và ít xâm phạm hơn.


Ngoại lệ

Lời hứa có liên quan mật thiết đến ngoại lệ. Chỉ riêng giao diện của một lời hứa là không đủ để truyền tải hoàn toàn trạng thái của nó, vì vậy các ngoại lệ được đưa ra bất cứ khi nào một thao tác trên một lời hứa không có ý nghĩa. Tất cả các ngoại lệ là loại std::future_error, xuất phát từ std::logic_error. Trước hết, một mô tả về một số hạn chế:

  • Một lời hứa được xây dựng mặc định là không hoạt động. Lời hứa không hoạt động có thể chết mà không có hậu quả.

  • Một lời hứa sẽ hoạt động khi có được một tương lai thông qua get_future(). Tuy nhiên, chỉ có một tương lai có thể có được!

  • Một lời hứa phải được thỏa mãn thông qua set_value()hoặc có một ngoại lệ được đặt ra set_exception()trước khi thời gian tồn tại của nó kết thúc nếu tương lai của nó được tiêu thụ. Một lời hứa hài lòng có thể chết mà không có hậu quả, và get()sẽ có sẵn trong tương lai. Một lời hứa với một ngoại lệ sẽ đưa ra ngoại lệ được lưu trữ theo yêu cầu của get()tương lai. Nếu lời hứa chết không có giá trị cũng không ngoại lệ, kêu gọi get()tương lai sẽ đưa ra ngoại lệ "thất hứa".

Dưới đây là một loạt thử nghiệm nhỏ để chứng minh những hành vi đặc biệt khác nhau này. Đầu tiên, khai thác:

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

Bây giờ vào các bài kiểm tra.

Trường hợp 1: Lời hứa không hoạt động

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

Trường hợp 2: Lời hứa tích cực, không sử dụng

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

Trường hợp 3: Quá nhiều tương lai

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

Trường hợp 4: Lời hứa thỏa mãn

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

Trường hợp 5: Quá nhiều sự hài lòng

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

Trường hợp ngoại lệ cùng được ném nếu có nhiều hơn một trong hai của set_valuehay set_exception.

Trường hợp 6: Ngoại lệ

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

Trường hợp 7: Hứa hẹn

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

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

    return fut.get();
}

Bạn đã nói "... có hiệu quả đồng bộ hóa chương trình bằng cách chờ kết quả." . "Đồng bộ hóa" nghĩa là gì ở đây? Toàn bộ tuyên bố có ý nghĩa gì? Tôi không thể hiểu điều này. Không có ý nghĩa của "đồng bộ hóa" từ mục từ điển này giúp tôi hiểu câu. Có phải "chờ" có nghĩa là "đồng bộ hóa" không? Có phải mọi sự chờ đợi đều đồng bộ hóa? Tôi nghĩ rằng tôi phần nào hiểu được những gì bạn có ý nghĩa, nhưng tôi không chắc chắn những gì bạn thực sự có ý nghĩa.
Nawaz

9
Câu trả lời hay, cảm ơn vì sự giúp đỡ của bạn
StereoMatching

1
@FelixDombek: Chuyển tiếp hoàn hảo, vv std::functioncó nhiều nhà xây dựng; không có lý do để không tiếp xúc với những người tiêu dùng my_task.
Kerrek SB

1
@DaveedV.: Cảm ơn bạn đã phản hồi! Vâng, đó là trường hợp thử nghiệm 7: Nếu bạn phá hủy lời hứa mà không đặt giá trị hoặc ngoại lệ, thì việc gọi get()vào tương lai sẽ tạo ra ngoại lệ. Tôi sẽ làm rõ điều này bằng cách thêm "trước khi nó bị phá hủy"; xin vui lòng cho tôi biết nếu điều đó là đủ rõ ràng.
Kerrek SB

3
Cuối cùng, got()tôi tìm kiếm futurethư viện hỗ trợ chủ đề về promiselời giải thích tuyệt vời của bạn!
trăng nắng

33

Bartosz Milewski cung cấp một bài viết tốt.

C ++ chia việc thực hiện tương lai thành một tập hợp các khối nhỏ

std :: lời hứa là một trong những phần này.

Một lời hứa là một phương tiện để chuyển giá trị trả về (hoặc một ngoại lệ) từ luồng thực thi một chức năng sang luồng thu được trong tương lai của hàm.

...

Tương lai là đối tượng đồng bộ hóa được xây dựng xung quanh đầu nhận của kênh hứa hẹn.

Vì vậy, nếu bạn muốn sử dụng một tương lai, bạn kết thúc với một lời hứa mà bạn sử dụng để có được kết quả xử lý không đồng bộ.

Một ví dụ từ trang này là:

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
Nhìn thấy lời hứa trong hàm tạo của luồng cuối cùng đã làm cho đồng xu giảm xuống. Bài viết của Bartosz có thể không phải là lớn nhất, nhưng nó giải thích làm thế nào các yếu tố gắn kết với nhau. Cảm ơn.
Kerrek SB

28

Trong một xấp xỉ thô bạn có thể coi std::promiselà đầu kia của một std::future(điều này là sai , nhưng để minh họa bạn có thể nghĩ như thể nó là). Đầu cuối của người tiêu dùng của kênh truyền thông sẽ sử dụng a std::futuređể tiêu thụ dữ liệu từ trạng thái chia sẻ, trong khi luồng của nhà sản xuất sẽ sử dụng std::promiseđể ghi vào trạng thái chia sẻ.


12
@KerrekSB: std::asyncvề mặt khái niệm (điều này không bắt buộc theo tiêu chuẩn) được hiểu là một hàm tạo ra std::promise, đẩy nó vào một nhóm luồng (trong số các loại, có thể là một nhóm luồng, có thể là một luồng mới, ...) và trả về liên quan std::futuređến người gọi. Về phía khách hàng, bạn sẽ đợi std::futurevà một luồng ở đầu bên kia sẽ tính kết quả và lưu trữ nó trong std::promise. Lưu ý: tiêu chuẩn yêu cầu trạng thái chia sẻstd::futurenhưng không tồn tại của std::promisetrường hợp sử dụng cụ thể này.
David Rodríguez - dribeas

6
@KerrekSB: std::futuresẽ không gọi jointrên luồng, nó có một con trỏ đến trạng thái chia sẻ là bộ đệm giao tiếp thực tế. Các nhà nước chia sẻ có một cơ chế đồng bộ (có thể là std::function+ std::condition_variableđể khóa người gọi cho đến khi std::promiseđược hoàn thành. Việc thực hiện các chủ đề là trực giao với tất cả điều này, và trong việc triển khai nhiều bạn có thể thấy rằng std::asynckhông được thực hiện bởi chủ đề mới mà sau đó được tham gia, nhưng thay vì một nhóm chủ đề có thời gian tồn tại đến hết chương trình.
David Rodríguez - dribeas

1
@ DavidRodríguez-dribeas: vui lòng chỉnh sửa thông tin từ các bình luận thành câu trả lời của bạn.
Marc Mutz - mmutz

2
@JonathanWakely: Điều đó không có nghĩa là nó phải được thực thi trong một luồng mới, chỉ có điều nó phải được thực thi không đồng bộ như thể nó được chạy trong một luồng mới được tạo. Ưu điểm chính std::asynclà thư viện thời gian chạy có thể đưa ra quyết định đúng đắn cho bạn về số lượng luồng cần tạo và trong hầu hết các trường hợp, tôi sẽ mong đợi các thời gian chạy sử dụng nhóm luồng. Hiện tại VS2012 không sử dụng nhóm luồng dưới mui xe và nó không vi phạm quy tắc as-if . Lưu ý rằng có những đảm bảo rất ít mà cần phải được thực hiện cho điều này đặc biệt như-nếu .
David Rodríguez - dribeas

1
Các chủ đề cục bộ cần được khởi tạo lại, nhưng quy tắc as-if cho phép mọi thứ (đó là lý do tại sao tôi đặt chữ "như thể" in nghiêng :)
Jonathan Wakely

11

std::promiselà kênh hoặc đường dẫn thông tin được trả về từ hàm async. std::futurelà cơ chế đồng bộ hóa làm cho người gọi chờ cho đến khi giá trị trả về được mang theo std::promisesẵn sàng (có nghĩa là giá trị của nó được đặt bên trong hàm).


8

Thực sự có 3 thực thể cốt lõi trong xử lý không đồng bộ. C ++ 11 hiện đang tập trung vào 2 trong số đó.

Những điều cốt lõi bạn cần để chạy một số logic không đồng bộ là:

  1. Tác vụ (logic được đóng gói dưới dạng một số đối tượng functor) sẽ CHẠY 'ở đâu đó'.
  2. Các nút thực tế chế biến - một chủ đề, một quá trình vv chạy functors như vậy khi họ được cung cấp cho nó. Nhìn vào mẫu thiết kế "Lệnh" để biết ý tưởng tốt về cách nhóm luồng công nhân cơ bản thực hiện việc này.
  3. Các kết quả xử lý : Ai đó cần kết quả đó, và cần một đối tượng mà sẽ GET đó cho họ. Vì OOP và các lý do khác, mọi chờ đợi hoặc đồng bộ hóa phải được thực hiện trong API của xử lý này.

C ++ 11 gọi những điều tôi nói ở (1) std::promisevà những điều trong (3) std::future. std::threadlà điều duy nhất được cung cấp công khai cho (2). Điều này thật đáng tiếc vì các chương trình thực sự cần quản lý tài nguyên luồng & bộ nhớ và hầu hết sẽ muốn các tác vụ chạy trên nhóm luồng thay vì tạo và hủy luồng cho mọi tác vụ nhỏ (hầu như luôn gây ra các lần truy cập hiệu năng không cần thiết và có thể dễ dàng tạo tài nguyên chết đói mà còn tồi tệ hơn).

Theo Herb Sutter và những người khác trong bộ não C ++ 11, có những kế hoạch dự kiến ​​để thêm một std::executorthứ giống như trong Java - sẽ là cơ sở cho các nhóm luồng và các thiết lập tương tự logic cho (2). Có thể chúng ta sẽ thấy nó trong C ++ 2014, nhưng đặt cược của tôi giống với C ++ 17 hơn (và Chúa sẽ giúp chúng ta nếu họ làm hỏng tiêu chuẩn cho những điều này).


7

A std::promiseđược tạo như một điểm kết thúc cho một cặp lời hứa / tương lai và std::future(được tạo từ std :: lời hứa bằng get_future()phương thức) là điểm kết thúc khác. Đây là một phương pháp đơn giản, một lần bắn cung cấp một cách để hai luồng đồng bộ hóa khi một luồng cung cấp dữ liệu cho một luồng khác thông qua một tin nhắn.

Bạn có thể nghĩ về nó như một luồng tạo ra một lời hứa để cung cấp dữ liệu và luồng khác thu thập lời hứa trong tương lai. Cơ chế này chỉ có thể được sử dụng một lần.

Cơ chế hứa hẹn / tương lai chỉ là một hướng, từ luồng sử dụng set_value()phương thức a std::promiseđến luồng sử dụng luồng get()a std::futuređể nhận dữ liệu. Một ngoại lệ được tạo nếuget() phương thức của một tương lai được gọi nhiều lần.

Nếu thread với std::promiseđã không được sử dụng set_value()để thực hiện lời hứa của mình sau đó khi các cuộc gọi chủ đề thứ hai get()của std::futuređể thu thập các lời hứa, thread thứ hai sẽ đi vào trạng thái chờ đợi cho đến khi lời hứa được thực hiện bởi các chủ đề đầu tiên với std::promisekhi nó sử dụng các set_value()phương pháp để gửi dữ liệu.

Với các phần tử được đề xuất của Đặc tả kỹ thuật Ngôn ngữ lập trình N4663 - Phần mở rộng C ++ cho Coroutines và hỗ trợ trình biên dịch Visual Studio 2017 C ++ co_await, cũng có thể sử dụng std::futurestd::asyncviết chức năng coroutine. Xem các cuộc thảo luận và ví dụ trong https://stackoverflow.com/a/50753040/1466970 có một phần thảo luận về việc sử dụng std::futurevới co_await.

Mã ví dụ sau đây, một ứng dụng bảng điều khiển Windows Visual Studio 2013 đơn giản, hiển thị bằng cách sử dụng một vài lớp / mẫu đồng thời C ++ 11 và các chức năng khác. Nó minh họa việc sử dụng cho lời hứa / tương lai hoạt động tốt, các luồng tự trị sẽ thực hiện một số nhiệm vụ và dừng lại, và sử dụng khi cần có hành vi đồng bộ hơn và do cần nhiều thông báo, cặp lời hứa / tương lai không hoạt động.

Một lưu ý về ví dụ này là sự chậm trễ được thêm vào ở những nơi khác nhau. Những độ trễ này chỉ được thêm vào để đảm bảo rằng các thông báo khác nhau được in trên bảng điều khiển sử dụng std::coutsẽ rõ ràng và văn bản từ một số luồng sẽ không bị xen kẽ.

Phần đầu tiên của việc main()tạo ba luồng bổ sung và sử dụng std::promisestd::futuregửi dữ liệu giữa các luồng. Một điểm thú vị là ở đó luồng chính khởi động một luồng, T2, sẽ chờ dữ liệu từ luồng chính, làm một cái gì đó, sau đó gửi dữ liệu đến luồng thứ ba, T3, sau đó sẽ làm một cái gì đó và gửi dữ liệu trở lại chủ đề chính.

Phần thứ hai của việc main()tạo hai luồng và một tập hợp các hàng đợi để cho phép nhiều tin nhắn từ luồng chính đến mỗi luồng được tạo. Chúng tôi không thể sử dụng std::promisestd::futurevì điều này bởi vì bộ đôi lời hứa / tương lai là một lần và không thể sử dụng nhiều lần.

Nguồn của lớp học Sync_queuelà từ Ngôn ngữ lập trình C ++ của Stroustrup: Phiên bản thứ 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;
}

Ứng dụng đơn giản này tạo ra đầu ra sau đây.

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

Lời hứa là đầu dây bên kia.

Hãy tưởng tượng bạn cần lấy giá trị của một futuređược tính bởi một async. Tuy nhiên, bạn không muốn nó được tính toán trong cùng một luồng và thậm chí bạn không sinh ra một luồng "ngay bây giờ" - có thể phần mềm của bạn được thiết kế để chọn một luồng từ một nhóm, vì vậy bạn không biết ai sẽ thực hiện tính toán cuối cùng.

Bây giờ, những gì bạn chuyển đến chủ đề / lớp / thực thể này (chưa biết)? Bạn không vượt qua future, vì đây là kết quả . Bạn muốn vượt qua một cái gì đó được kết nối với futurevà đại diện cho đầu kia của dây , vì vậy bạn sẽ chỉ truy vấn mà futurekhông có kiến ​​thức về người sẽ thực sự tính toán / viết một cái gì đó.

Đây là promise. Nó là một tay cầm kết nối với của bạn future. Nếu futureloa và với get()bạn bắt đầu nghe cho đến khi một âm thanh nào đó phát ra, đó promisemicrô ; nhưng không chỉ bất kỳ microphone, nó là các microphone kết nối với một sợi dây duy nhất để các loa bạn giữ. Bạn có thể biết ai ở đầu kia nhưng bạn không cần biết - bạn chỉ cần đưa nó và đợi cho đến khi bên kia nói điều gì đó.


0

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

Một câu giải thích: furture :: get () wa promse :: set_value () mãi mãi.

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.