std::async, std::future & std::promise
Sử dụng tính năng trừu tượng hóa đồng thời cấp cao hơn: khởi chạy các tác vụ không đồng bộ với std::async, truy xuất kết quả bằng std::future và đặt thủ công các giá trị hoặc ngoại lệ với std::promise.
Đồng thời dựa trên nhiệm vụ
Mặc dùstd::threadcung cấp cho bạn khả năng kiểm soát ở mức độ thấp, đồng thời dựa trên tác vụ vớistd::asyncvàstd::futurethường dễ dàng hơn và ít xảy ra lỗi hơn. Thay vì quản lý các luồng và trạng thái chia sẻ theo cách thủ công, bạn thể hiện công việc dưới dạng các nhiệm vụ tạo ra kết quả. Thời gian chạy quyết định cách lên lịch cho chúng.
std::futuređại diện cho giá trị thuộc loạiTsẽ có sẵn trong tương lai. Bạn có thể gọiget()để chặn cho đến khi có kết quả. Nếu tác vụ đưa ra một ngoại lệ,get()sẽ thực hiện lại ngoại lệ đó — mang đến cho bạn khả năng truyền ngoại lệ tự động trên các luồng.
std::async — Khởi chạy các tác vụ không đồng bộ
std::asynckhởi chạy một lệnh có thể gọi và trả vềstd::futurechứa kết quả. Bạn có thể chỉ định chính sách khởi chạy:std::launch::asyncbuộc một luồng mới, trong khistd::launch::deferredtrì hoãn việc thực thi cho đến khiget()được gọi.
#include <iostream>
#include <future>
#include <chrono>
#include <numeric>
#include <vector>
long long parallel_sum(const std::vector<int>& v, size_t start, size_t end) {
if (end - start < 1000) {
return std::accumulate(v.begin() + start, v.begin() + end, 0LL);
}
size_t mid = start + (end - start) / 2;
// Launch left half asynchronously
auto left = std::async(std::launch::async,
parallel_sum, std::cref(v), start, mid);
// Compute right half in this thread
long long right_sum = parallel_sum(v, mid, end);
return left.get() + right_sum; // .get() blocks until left is done
}
int main() {
std::vector<int> data(1'000'000, 1);
auto start = std::chrono::high_resolution_clock::now();
long long result = parallel_sum(data, 0, data.size());
auto end = std::chrono::high_resolution_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Sum: " << result << " in " << ms.count() << "ms\n";
return 0;
}Chính sách khởi chạy: không đồng bộ và hoãn lại
std::launch::async — Hàm được thực thi trên một luồng mới (hoặc một luồng từ nhóm luồng, do quá trình triển khai xác định). Công việc bắt đầu ngay lập tức.
std::launch::deferred — Hàm này không được thực thi cho đến khiget()hoặcwait()được gọi trong tương lai. Nó chạy trong luồng gọi (đánh giá lười biếng). Hữu ích cho các tính toán mà bạn có thể không cần.
Mặc định (không có chính sách nào được chỉ định) — Việc triển khai được chọn. Đây có thể làasync,deferredhoặc cả hai (có nghĩa là nó quyết định khi chạy). Nếu bạn cần đảm bảo tính song song, hãy luôn chỉ địnhstd::launch::async.
std::promise — Cài đặt kết quả thủ công
std::promiseis the write end of a future-promise channel. You create a promise, extract its future, and then eitherset_value()orset_exception()from any thread.
#include <iostream>
#include <thread>
#include <future>
#include <stdexcept>
void compute(std::promise<int> prom, int x) {
try {
if (x < 0) {
throw std::invalid_argument("negative input");
}
int result = x * x;
prom.set_value(result); // fulfil the promise
} catch (...) {
prom.set_exception(std::current_exception()); // propagate error
}
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future(); // get the read end
std::thread t(compute, std::move(prom), 7);
try {
int result = fut.get(); // blocks until value or exception
std::cout << "Result: " << result << "\n"; // prints 49
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << "\n";
}
t.join();
return 0;
}std::shared_future - Nhiều người tiêu dùng
std::futurechỉ có thể làget()-ed một lần (nó di chuyển giá trị ra ngoài).std::shared_futurecho phép nhiều luồng chờ và đọc cùng một kết quả.
#include <iostream>
#include <future>
#include <thread>
#include <vector>
int main() {
auto shared = std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}).share(); // convert future to shared_future
// Multiple threads can all read the same result
std::vector<std::thread> consumers;
for (int i = 0; i < 5; ++i) {
consumers.emplace_back([shared, i]() {
int val = shared.get(); // all 5 threads get 42
std::cout << "Consumer " << i << " got: " << val << "\n";
});
}
for (auto& t : consumers) t.join();
return 0;
}std::futuređượcstd::asynctrả về có một thuộc tính đặc biệt: hàm hủy của nó chặn cho đến khi tác vụ hoàn thành (nhưjoinẩn). Điều này có nghĩa là nếu bạn loại bỏ tương lai được trả về, lệnh gọi "không đồng bộ" sẽ trở nên đồng bộ một cách hiệu quả:
// SURPRISE — this blocks! The temporary future is destroyed
// at the semicolon, which waits for the task to finish.
std::async(std::launch::async, long_computation);
// CORRECT — store the future to let work proceed in parallel
auto fut = std::async(std::launch::async, long_computation);
// ... do other work ...
fut.get(); // now wait for the resultThis is the most common pitfall with std::async. Always capture the returned std::future.
std::packaged_task
std::packaged_taskgói một lệnh gọi được và cung cấpstd::futurecho kết quả của nó. Không giống nhưstd::async, nó không tự động chạy tác vụ — bạn gọi nó theo cách thủ công (thường bằng cách di chuyển nó vào một luồng hoặc hàng đợi tác vụ). Điều này lý tưởng cho việc xây dựng nhóm chủ đề tùy chỉnh:
1. Gói công việc trongstd::packaged_task.
2. Giải nénstd::futurevà đưa cho người gọi.
3. Chuyển tác vụ vào hàng đợi công việc.
4. Các luồng công nhân kéo các tác vụ từ hàng đợi và gọi chúng.
Sử dụngstd::asynckhi bạn muốn có kết quả từ công việc đồng thời và không cần kiểm soát luồng chi tiết. Nó tự động truyền bá các ngoại lệ và quản lý vòng đời của luồng. Sử dụngstd::thread(hoặcstd::jthread) khi bạn cần nhân viên nền liên tục, lập lịch tùy chỉnh hoặc khi bạn đang xây dựng cơ sở hạ tầng như nhóm luồng. Đối với công việc dễ quên, hãy luôn giữstd::futuređược trả lại để tránh bị chặn do vô tình.
std::asynckhởi chạy một tác vụ và trả vềstd::future— một giải pháp thay thế cấp cao cho việc quản lý luồng thủ côngstd::launch::asyncbuộc một luồng mới;std::launch::deferredtrì hoãn cho đến khiget()được gọistd::promiselà phần cuối ghi của kênh trong tương lai - sử dụng nó để cài đặt giá trị/ngoại lệ thủ côngstd::shared_futurecho phép nhiều luồng đọc cùng một kết quảstd::futuretừstd::asyncchặn trong hàm hủy của nó - luôn lưu trữ nó trong một biếnstd::packaged_taskkết hợp một khả năng có thể gọi được với tương lai, lý tưởng cho các nhóm luồng tùy chỉnh
Câu đố — Kiểm tra kiến thức
(15 XP)1. Điều gì xảy ra nếu bạn loại bỏ`std::future`được`std::async(std::launch::async, fn)`trả về?
2. Sự khác biệt giữa`std::future`và`std::shared_future`là gì?
3. `std::async`truyền bá các ngoại lệ từ tác vụ tới người gọi như thế nào?