Nội dung
Trong bài viết này, ta sẽ thảo luận cách thực thi bất đồng bộ sử dụng std::async trong C++11.
std::async() là gì?
std::async() là một hàm template chấp nhận một callback (tức hàm hoặc đối tượng hàm) làm đối số và có khả năng thực thi chúng không đồng bộ.
template <class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type> async (launch policy, Fn&& fn, Args&&... args);
std::async trả về một std::future<T>, lưu trữ giá trị được trả về bởi đối tượng hàm được thực thi bởi std::async(). Các đối số của callback có thể được truyền cho std::async() dưới dạng đối số sau đối số con trỏ hàm.
Đối số đầu tiên trong std::async là chính sách khởi chạy, nó kiểm soát hành vi không đồng bộ của std::async. Chúng ta có thể tạo std::async với 3 chính sách khởi chạy khác nhau,
- std::launch::async: Nó đảm bảo thực thi không đồng bộ, tức là hàm đã truyền sẽ được thực thi trong thread riêng biệt.
- std::launch::deferred: Nó không phải là thực thi không đồng bộ, tức là hàm callback sẽ được gọi khi thread khác sẽ gọi get() trong tương lai để truy cập trạng thái chia sẻ.
- std::launch::async | std::launch::deferred: Đây là mặc định. Với chính sách khởi chạy này, nó có thể chạy không đồng bộ hay không tùy thuộc vào tải trên hệ thống. Tuy nhiên chúng ta không có quyền kiểm soát nó.
Nếu chúng ta không chỉ định một chính sách khởi chạy. Cách thực thi của nó sẽ tương tự như std::launch::async | std::launch::deferred. Chúng ta sẽ sử dụng chính sách std::launch::async trong bài viết này. Ta có thể truyền bất kỳ hàm callback nào trong std::async, như là con trỏ hàm, đối tượng hàm hay lambda.
Sự cần thiết của std::async()
Giả sử chúng ta phải tìm nạp một số dữ liệu (string) từ DB và một số từ các file trong hệ thống file. Sau đó, ta cần hợp nhất cả chuỗi và in ra.
Nếu thực thi trong một thread đơn, ta có sẽ có như sau,
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
using namespace std::chrono;
std::string fetchDataFromDB(std::string recvdData)
{
// Make sure that function takes 5 seconds to complete
std::this_thread::sleep_for(seconds(5));
//Do stuff like creating DB Connection and fetching Data
return "DB_" + recvdData;
}
std::string fetchDataFromFile(std::string recvdData)
{
// Make sure that function takes 5 seconds to complete
std::this_thread::sleep_for(seconds(5));
//Do stuff like fetching Data File
return "File_" + recvdData;
}
int main()
{
// Get Start Time
system_clock::time_point start = system_clock::now();
//Fetch Data from DB
std::string dbData = fetchDataFromDB("Data");
//Fetch Data from File
std::string fileData = fetchDataFromFile("Data");
// Get End Time
auto end = system_clock::now();
auto diff = duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time Taken = " << diff << " Seconds" << std::endl;
//Combine The Data
std::string data = dbData + " :: " + fileData;
//Printing the combined Data
std::cout << "Data = " << data << std::endl;
return 0;
}
Kết quả là:
Total Time Taken = 10 Seconds
Data = DB_Data :: File_Data
Vì cả hai hàm fetchDataFromDB() và fetchDataFromFile() mất 5 giây mỗi hàm và chỉ chạy trong một threa duy nhất, do đó, tổng thời gian tiêu thụ sẽ là 10 giây.
Bây giờ việc fetch dữ liệu từ DB và file độc lập với nhau và cũng tốn thời gian. Vì vậy, ta có thể chạy chúng một cách đồng thời. Đó là tạo một thread mới chuyển môt std::promise làm đối số và tìm nạp dữ liệu từ std::future được liên kết trong lời gọi thread.
Một cách dễ dàng khác là sử dụng std::async.
Gọi std::async với con trỏ hàm như là một callback
Bây giờ, hãy sửa đổi chương trình trên và gọi fetchDataFromDB() không đồng bộ bằng cách sử dụng std::async()
std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
// Do Some Stuff
// Fetch Data from DB
// Will block till data is available in future<std::string> object.
std::string dbData = resultFromDB.get();
std::async() thực hiện theo những điều sau đây,
- Nó tự động tạo ra một thread (hoặc chọn từ thread pool) và một đối tượng promise cho chúng ta.
- Sau đó chuyển đối tượng std::promise cho thread và trả về đối tượng std::future.
- Khi hàm fetchDataFromDB() thoát thì giá trị của nó sẽ được set cho đối tượng std::promise này, vì vậy cuối cùng giá trị trả về sẽ có sẵn trong đối tượng std::future.
Hãy xem đoạn chương trình sử dụng std::async sau đây,
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
using namespace std::chrono;
std::string fetchDataFromDB(std::string recvdData)
{
// Make sure that function takes 5 seconds to complete
std::this_thread::sleep_for(seconds(5));
//Do stuff like creating DB Connection and fetching Data
return "DB_" + recvdData;
}
std::string fetchDataFromFile(std::string recvdData)
{
// Make sure that function takes 5 seconds to complete
std::this_thread::sleep_for(seconds(5));
//Do stuff like fetching Data File
return "File_" + recvdData;
}
int main()
{
// Get Start Time
system_clock::time_point start = system_clock::now();
std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
//Fetch Data from File
std::string fileData = fetchDataFromFile("Data");
//Fetch Data from DB
// Will block till data is available in future<std::string> object.
std::string dbData = resultFromDB.get();
// Get End Time
auto end = system_clock::now();
auto diff = duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time Taken = " << diff << " Seconds" << std::endl;
//Combine The Data
std::string data = dbData + " :: " + fileData;
//Printing the combined Data
std::cout << "Data = " << data << std::endl;
return 0;
}
Bây giờ, thời gian chạy chỉ là 5 giây.
Total Time Taken = 5 Seconds
Data = DB_Data :: File_Data
Gọi std::async với đối tượng hàm như là một callback
/*
* Function Object
*/
struct DataFetcher
{
std::string operator()(std::string recvdData)
{
// Make sure that function takes 5 seconds to complete
std::this_thread::sleep_for (seconds(5));
//Do stuff like fetching Data File
return "File_" + recvdData;
}
};
//Calling std::async with function object
std::future<std::string> fileResult = std::async(DataFetcher(), "Data");
Gọi std::async với hàm lambda như là một callback
//Calling std::async with lambda function
std::future<std::string> resultFromDB = std::async([](std::string recvdData){
std::this_thread::sleep_for (seconds(5));
//Do stuff like creating DB Connection and fetching Data
return "DB_" + recvdData;
}, "Data");
Để lại một bình luận