Sự khác biệt giữa pack_task và async là gì


134

Trong khi làm việc với mô hình luồng của C ++ 11, tôi nhận thấy rằng

std::packaged_task<int(int,int)> task([](int a, int b) { return a + b; });
auto f = task.get_future();
task(2,3);
std::cout << f.get() << '\n';

auto f = std::async(std::launch::async, 
    [](int a, int b) { return a + b; }, 2, 3);
std::cout << f.get() << '\n';

dường như làm chính xác điều tương tự Tôi hiểu rằng có thể có một sự khác biệt lớn nếu tôi chạy std::asynctheo std::launch::deferred, nhưng có một trường hợp nào trong trường hợp này không?

Sự khác biệt giữa hai cách tiếp cận này là gì và quan trọng hơn, trong trường hợp sử dụng nào tôi nên sử dụng phương pháp này so với phương pháp khác?

Câu trả lời:


161

Trên thực tế, ví dụ bạn vừa đưa ra cho thấy sự khác biệt nếu bạn sử dụng một hàm khá dài, chẳng hạn như

//! sleeps for one second and returns 1
auto sleep = [](){
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 1;
};

Nhiệm vụ đóng gói

Bạn packaged_tasksẽ không tự mình bắt đầu, bạn phải gọi nó:

std::packaged_task<int()> task(sleep);

auto f = task.get_future();
task(); // invoke the function

// You have to wait until task returns. Since task calls sleep
// you will have to wait at least 1 second.
std::cout << "You can see this after 1 second\n";

// However, f.get() will be available, since task has already finished.
std::cout << f.get() << std::endl;

std::async

Mặt khác, std::asyncvới launch::asyncsẽ cố gắng chạy tác vụ trong một luồng khác:

auto f = std::async(std::launch::async, sleep);
std::cout << "You can see this immediately!\n";

// However, the value of the future will be available after sleep has finished
// so f.get() can block up to 1 second.
std::cout << f.get() << "This will be shown after a second!\n";

Hạn chế

Nhưng trước khi bạn cố gắng sử dụng asynccho tất cả mọi thứ, hãy nhớ rằng tương lai được trả lại có trạng thái chia sẻ đặc biệt, đòi hỏi phải future::~futurengăn chặn:

std::async(do_work1); // ~future blocks
std::async(do_work2); // ~future blocks

/* output: (assuming that do_work* log their progress)
    do_work1() started;
    do_work1() stopped;
    do_work2() started;
    do_work2() stopped;
*/

Vì vậy, nếu bạn muốn không đồng bộ thực sự, bạn cần giữ lại trả lại futurehoặc nếu bạn không quan tâm đến kết quả nếu hoàn cảnh thay đổi:

{
    auto pizza = std::async(get_pizza);
    /* ... */
    if(need_to_go)
        return;          // ~future will block
    else
       eat(pizza.get());
}   

Để biết thêm thông tin về điều này, hãy xem bài viết của Herb Sutter async~future , trong đó mô tả vấn đề, và Scott Meyer std::futurestừ std::asynckhông đặc biệt , trong đó mô tả những hiểu biết. Cũng lưu ý rằng hành vi này đã được chỉ định trong C ++ 14 trở lên , nhưng cũng thường được thực hiện trong C ++ 11.

Khác biệt hơn nữa

Bằng cách sử dụng, std::asyncbạn không thể chạy tác vụ của mình trên một luồng cụ thể nữa, nơi std::packaged_taskcó thể được chuyển sang các luồng khác.

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);

std::cout << f.get() << "\n";

Ngoài ra, packaged_taskcần phải được gọi trước khi bạn gọi f.get(), nếu không chương trình của bạn sẽ đóng băng vì tương lai sẽ không bao giờ sẵn sàng:

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);

TL; DR

Sử dụng std::asyncnếu bạn muốn thực hiện một số việc và không thực sự quan tâm khi hoàn thành và std::packaged_tasknếu bạn muốn kết thúc mọi thứ để chuyển chúng sang các chủ đề khác hoặc gọi chúng sau. Hoặc, để trích dẫn Christian :

Cuối cùng, a std::packaged_taskchỉ là một tính năng cấp thấp hơn để thực hiện std::async(đó là lý do tại sao nó có thể làm được nhiều hơn std::asyncnếu được sử dụng cùng với các công cụ cấp thấp khác, như std::thread). Nói một cách đơn giản std::packaged_tasklà một std::functionliên kết đến a std::futurestd::asynckết thúc và gọi một std::packaged_task(có thể trong một luồng khác).


9
Bạn nên thêm rằng tương lai được trả về bởi các khối không đồng bộ khi hủy (như thể bạn gọi là get) trong khi tương lai được trả về từ gói_tool thì không.
John5342

22
Cuối cùng, a std::packaged_taskchỉ là một tính năng cấp thấp hơn để thực hiện std::async(đó là lý do tại sao nó có thể làm được nhiều hơn std::asyncnếu được sử dụng cùng với các công cụ cấp thấp khác, như std::thread). Nói một cách đơn giảnstd::packaged_task là một std::functionliên kết đến a std::futurestd::asynckết thúc và gọi một std::packaged_task(có thể trong một luồng khác).
Christian Rau

Tôi đang làm một số thí nghiệm trên khối ~ tương lai (). Tôi không thể sao chép hiệu ứng chặn đối với việc phá hủy đối tượng trong tương lai. Tất cả mọi thứ làm việc không đồng bộ. Tôi đang sử dụng VS 2013 và khi tôi khởi chạy async, tôi đã sử dụng std :: launch :: async. VC ++ bằng cách nào đó đã "khắc phục" vấn đề này?
Frank Liu

1
@FrankLiu: Chà, N3451 là một đề xuất được chấp nhận, mà (theo như tôi biết) đã đi vào C ++ 14. Cho rằng Herb hoạt động tại Microsoft, tôi sẽ không ngạc nhiên nếu tính năng đó được triển khai trong VS2013. Một trình biên dịch tuân thủ nghiêm ngặt các quy tắc C ++ 11 vẫn sẽ hiển thị hành vi này.
Zeta

1
@Mikhail Câu trả lời này có trước cả C ++ 14 và C ++ 17, vì vậy tôi không có các tiêu chuẩn mà chỉ có các đề xuất trong tay. Tôi sẽ xóa đoạn văn.
Zeta

1

Nhiệm vụ đóng gói so với async

p> Nhiệm vụ đóng gói giữ một[function or function object]cặpnhiệm vụvà tương lai / lời hứa. Khi nhiệm vụ thực hiện một tuyên bố trở lại, nó gây raset_value(..)trênpackaged_task's hứa hẹn.

a> Đưa ra nhiệm vụ tương lai, lời hứa và gói, chúng ta có thể tạo các tác vụ đơn giản mà không phải lo lắng quá nhiều về các luồng [luồng chỉ là thứ chúng ta đưa ra để chạy một tác vụ].

Tuy nhiên, chúng ta cần xem xét có bao nhiêu luồng sử dụng hoặc liệu một tác vụ được chạy tốt nhất trên luồng hiện tại hay trên một luồng khác, v.v. Có thể xử lý các mô tả của trình khởi chạy luồng async(), quyết định tạo một luồng mới hay tái chế một luồng cũ một hoặc chỉ đơn giản là chạy tác vụ trên luồng hiện tại. Nó trả lại một tương lai.


0

"Mẫu lớp std :: pack_task bao bọc mọi mục tiêu có thể gọi được (hàm, biểu thức lambda, biểu thức liên kết hoặc đối tượng hàm khác) để có thể được gọi không đồng bộ. Giá trị trả về hoặc ngoại lệ của nó được lưu trữ trong trạng thái chia sẻ có thể được truy cập thông qua std :: các đối tượng trong tương lai. "

"Hàm đồng bộ mẫu không đồng bộ chạy hàm f không đồng bộ (có khả năng trong một luồng riêng) và trả về một std :: tương lai cuối cùng sẽ giữ kết quả của lệnh gọi hàm đó."

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.