อะไรคือความแตกต่างระหว่าง packaged_task และ async


137

ในขณะที่ทำงานกับโมเดลเธรดของ C ++ 11 ฉันสังเกตเห็นว่า

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

ดูเหมือนจะทำในสิ่งเดียวกัน ฉันเข้าใจว่าอาจมีความแตกต่างที่สำคัญถ้าฉันวิ่งไปstd::asyncด้วยstd::launch::deferredแต่ในกรณีนี้มีไหม

อะไรคือความแตกต่างระหว่างสองวิธีนี้และที่สำคัญกว่านั้นฉันควรใช้วิธีใดในกรณีใดบ้าง

คำตอบ:


164

จริงๆแล้วตัวอย่างที่คุณเพิ่งให้แสดงความแตกต่างหากคุณใช้ฟังก์ชันที่ค่อนข้างยาวเช่น

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

งานแพ็กเกจ

A packaged_taskจะไม่เริ่มด้วยตัวเองคุณต้องเรียกใช้:

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

ในทางกลับกันstd::asyncด้วยlaunch::asyncจะพยายามเรียกใช้งานในเธรดอื่น:

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

ข้อเสียเปรียบ

แต่ก่อนที่คุณจะพยายามใช้asyncกับทุกสิ่งโปรดจำไว้ว่าอนาคตที่กลับมามีสถานะที่ใช้ร่วมกันพิเศษซึ่งต้องการให้future::~futureบล็อก:

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

ดังนั้นหากคุณต้องการอะซิงโครนัสจริงคุณต้องเก็บสิ่งที่ส่งคืนfutureไว้หรือหากคุณไม่สนใจผลลัพธ์หากสถานการณ์เปลี่ยนไป:

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้โปรดดูบทความของ Herb Sutter asyncและ~futureซึ่งอธิบายถึงปัญหาและ Scott Meyer std::futuresจากstd::asyncไม่พิเศษซึ่งอธิบายถึงข้อมูลเชิงลึก นอกจากนี้โปรดทราบว่าพฤติกรรมนี้ระบุไว้ใน C ++ 14 ขึ้นไปแต่ยังใช้งานได้ทั่วไปใน C ++ 11

ความแตกต่างเพิ่มเติม

เมื่อใช้std::asyncคุณไม่สามารถรันงานของคุณบนเธรดเฉพาะได้อีกต่อไปซึ่งstd::packaged_taskสามารถย้ายไปยังเธรดอื่นได้

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

นอกจากนี้packaged_taskจำเป็นต้องเรียกใช้ก่อนที่คุณจะโทรf.get()มิฉะนั้นคุณโปรแกรมจะหยุดทำงานเนื่องจากอนาคตจะไม่พร้อม:

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

TL; ดร

ใช้ในstd::asyncกรณีที่คุณต้องการให้บางสิ่งเสร็จสิ้นและไม่สนใจเมื่อทำเสร็จแล้วและstd::packaged_taskหากคุณต้องการสรุปสิ่งต่างๆเพื่อย้ายไปยังเธรดอื่นหรือโทรหาในภายหลัง หรืออ้างถึงคริสเตียน :

ในท้ายที่สุด a std::packaged_taskเป็นเพียงคุณลักษณะระดับล่างสำหรับการใช้งานstd::async(ซึ่งเป็นเหตุผลว่าทำไมจึงสามารถทำได้มากกว่าstd::asyncถ้าใช้ร่วมกับสิ่งอื่น ๆ ในระดับล่างเช่นstd::thread) พูดง่ายๆstd::packaged_taskคือ a std::functionเชื่อมโยงกับ a std::futureและstd::asyncตัดและเรียก a std::packaged_task(อาจอยู่ในเธรดอื่น)


9
คุณควรเพิ่มว่าอนาคตจะถูกส่งคืนโดย async บล็อกเมื่อถูกทำลาย (ราวกับว่าคุณเรียกว่า get) ในขณะที่สิ่งที่ส่งคืนจาก packaged_task ไม่ได้
John5342

24
ในท้ายที่สุด a std::packaged_taskเป็นเพียงคุณลักษณะระดับล่างสำหรับการใช้งานstd::async(ซึ่งเป็นเหตุผลว่าทำไมจึงสามารถทำได้มากกว่าstd::asyncถ้าใช้ร่วมกับสิ่งอื่น ๆ ในระดับล่างเช่นstd::thread) พูดง่ายๆstd::packaged_taskคือ a std::functionเชื่อมโยงกับ a std::futureและstd::asyncตัดและเรียก a std::packaged_task(อาจอยู่ในเธรดอื่น)
Christian Rau

ฉันกำลังทำการทดลองบางอย่างกับบล็อก ~ future () ฉันไม่สามารถจำลองเอฟเฟกต์การบล็อกของการทำลายวัตถุในอนาคตได้ ทุกอย่างทำงานแบบอะซิงโครนัส ฉันใช้ VS 2013 และเมื่อฉันเปิด async ฉันใช้ std :: launch :: async VC ++ "แก้ไข" ปัญหานี้หรือไม่
Frank Liu

1
@FrankLiu: อืม N3451 เป็นข้อเสนอที่ยอมรับซึ่ง (เท่าที่ฉันรู้) เข้าสู่ C ++ 14 เนื่องจาก Herb ทำงานที่ Microsoft ฉันจะไม่แปลกใจเลยถ้าฟีเจอร์นั้นถูกนำไปใช้ใน VS2013 คอมไพเลอร์ที่ปฏิบัติตามกฎ C ++ 11 อย่างเคร่งครัดจะยังคงแสดงพฤติกรรมนี้
Zeta

1
@Mikhail คำตอบนี้นำหน้าทั้ง C ++ 14 และ C ++ 17 ดังนั้นฉันจึงไม่มีมาตรฐาน แต่มีเพียงข้อเสนอเท่านั้น ฉันจะลบย่อหน้า
Zeta

1

งานแพ็กเกจเทียบกับ async

p>งานแพ็กเกจถืองาน[function or function object]และคู่อนาคต / สัญญา เมื่องานดำเนินการคำสั่งกลับจะทำให้set_value(..)ในpackaged_taskสัญญา 's

a>กำหนดอนาคตสัญญาและงานแพ็กเกจเราสามารถสร้างงานง่ายๆโดยไม่ต้องกังวลเกี่ยวกับเธรดมากเกินไป [เธรดเป็นเพียงสิ่งที่เรามอบให้เพื่อรันงาน]

อย่างไรก็ตามเราต้องพิจารณาจำนวนเธรดที่จะใช้หรือว่างานนั้นทำงานได้ดีที่สุดบนเธรดปัจจุบันหรือบนอื่น ๆ เป็นต้นการแยกส่วนดังกล่าวสามารถจัดการได้โดยตัวเรียกใช้เธรดที่เรียกว่าasync()ซึ่งจะตัดสินใจว่าจะสร้างเธรดใหม่หรือรีไซเคิลสิ่งเก่า หนึ่งหรือเพียงแค่รันงานบนเธรดปัจจุบัน ส่งคืนอนาคต


0

"เทมเพลตคลาส std :: packaged_task จะห่อหุ้มเป้าหมายที่เรียกได้ (ฟังก์ชันแลมบ์ดานิพจน์การผูกหรืออ็อบเจ็กต์ฟังก์ชันอื่น) เพื่อให้สามารถเรียกใช้แบบอะซิงโครนัสได้ค่าส่งคืนหรือข้อยกเว้นที่ถูกโยนจะถูกเก็บไว้ในสถานะที่ใช้ร่วมกันซึ่งสามารถเข้าถึงได้ ผ่าน std :: วัตถุในอนาคต "

"ฟังก์ชันเทมเพลต async เรียกใช้ฟังก์ชัน f แบบอะซิงโครนัส (อาจอยู่ในเธรดที่แยกจากกัน) และส่งคืน std :: future ซึ่งจะเก็บผลลัพธ์ของการเรียกฟังก์ชันนั้นในที่สุด"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.