一、參考 https://ericniebler.com/2020/11/08/structured-concurrency/ 二、總結 1. 結構化併發是什麼-概述 是什麼:一種確保子操作在父操作之前完成的方式,類似函數在調用函數之前完成。 最典型的結構化併發:C++20的協程 意義:它通過使非同步 ...
一、參考
https://ericniebler.com/2020/11/08/structured-concurrency/
二、總結
1. 結構化併發是什麼-概述
是什麼:一種確保子操作在父操作之前完成的方式,類似函數在調用函數之前完成。
最典型的結構化併發:C++20的協程
意義:它通過使非同步生存期與普通C++詞法作用域相對應,為非同步程式帶來了現代C++風格,並且不需要引用計數(智能指針,垃圾回收)來管理對象的生命周期
總結:即使在併發環境中,函數嵌套調用時參數的作用域也是嚴格嵌套的,不需要用智能指針(shared_ptr)之類的技術,也不會發生不小心的記憶體泄露--對象的生命周期
2. 為什麼需要結構化併發
2.1 結構化編程
具有嚴格的嵌套作用域和生命周期,併發情況下需考慮
2.2 多線程編程的困境
併發場景下編程難度大:
①對象生命周期已經結束,但是其他線程仍需要訪問,會導致懸空引用問題,此時需要智能指針通過引用計數來管理其生命周期
②與作用域相關的生命周期不清晰
2.3 非結構化併發
非結構化非同步編程很難保證正確性和效率,且編程複雜,且沒有辦法強制要求子任務在父任務之前完成
3. 結構化併發-深入
3.1 庫
協程:Lewis Baker’s popular cppcoro library
提升:
①只需要一個函數
②狀態保存在本地變數,而不需要保存在需要的引用計數的共用變數
③可使用一般的c++錯誤處理技術
④從結構保證了子操作在父操作之前完成
3.2 取消機制
從結構保證了子操作在父操作之前完成(也就是說,如果父操作先完成,需要等待子操作)
為了避免為不再需要結果的子操作等待不必要的長時間,我們需要一種能夠取消這些子操作的機制,以便它們快速完成。
3.3 結構化併發>協程
結構化併發:特定模式中的回調,可以在沒有協程的情況下實現結構化併發
4. 程式示例
場景:存在一個子操作,父任務需要此操作的結果
編譯運行環境:ubuntu22.04.3
①單線程:主線程等待子線程結束
耗時操作:computeResult
調用者:doThing
#include <iostream>
#include <unistd.h>
struct State{
int result;
};
// synchronous
void computeResult(State & s)
{
int time = 30;
sleep(time); // 用sleep模擬耗時操作
s.result = time;
}
int doThing() {
State s;
computeResult(s);
return s.result;
}
int main()
{
std::cout << "result: " << doThing() << std::endl;
}
// compile: g++ main.cpp
// output: result:30
關註State s;的聲明周期
②使用std::future:獲取子線程的結果
#include <future>
#include <iostream>
#include <unistd.h>
struct State{
int result;
};
int computeResult(State &s)
{
int time = 30;
sleep(time);
s.result = time;
std::cout << "p1" << std::endl;
return 1;
}
std::future<int> doThing()
{
std::cout << "p2" << std::endl;
State s;
std::future<int> fut = std::async(computeResult, s);
return fut;
}
int main()
{
std::cout << "p3" << std::endl;
auto fut = doThing();
std::cout << "result:" << fut.get() << std::endl;
}
// compile: g++ main.cpp
// 編譯階段不通過: 無法傳遞引用類型的函數參數
// main.cpp:24:38: error: no matching function for call to ‘async(int (&)(State&), State&)’
// 24 | std::future<int> fut = std::async(computeResult, s);
註意State s;的生命周期,使用智能指針shared_ptr管理State s;
#include <future>
#include <iostream>
#include <unistd.h>
struct State{
int result;
};
int computeResult(std::shared_ptr<State> s)
{
int time = 30;
sleep(time);
(*s).result = time;
std::cout << "p1" << std::endl;
return 1;
}
std::future<int> doThing()
{
std::cout << "p2" << std::endl;
std::shared_ptr<State> s = std::make_shared<State>();
std::future<int> fut = std::async(computeResult, s); // std::async可以傳遞帶有函數及其參數(參數是引用類型無法會出錯),但是boost::async傳不了多個參數-解決:使用std::bind綁定
return fut; // std::future沒有類似boost::future的then函數
}
int main()
{
std::cout << "p3" << std::endl;
auto fut = doThing();
std::cout << "result:" << fut.get() << std::endl;
}
// compile: g++ main.cpp
// output:
/*p3
p2
result:p1
1*/
②使用boost::future獲取結果:父操作需要子操作的結果
參考:http://www.bryh.cn/a/142977.html
註意:使用boost::future需要設置巨集#define BOOST_THREAD_PROVIDES_FUTURE
// 寫法一
#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
#include <iostream>
struct State{
int result;
};
// asynchronous
boost::future<void> computeResult(State &s)
{
int time = 30;
sleep(time);
s.result = time;
std::cout << "p1" << std::endl;
}
boost::future<int> doThing() {
State s;
auto fut = computeResult(s);
std::cout << "p2" << std::endl;
return fut.then(
[&](auto&&) { return s.result; }); //OOPS
}
int main()
{
std::cout << "p3" << std::endl;
auto fut = doThing();
std::cout << "result:" << fut.get() << std::endl;
}
// compile: g++ main.cpp -lboost_thread
// output:
/*
p3
p1
p2
terminate called after throwing an instance of 'boost::wrapexcept<boost::lock_error>'
what(): boost: mutex lock failed in pthread_mutex_lock: Invalid argument
已放棄 (核心已轉儲)
*/
// 寫法二
#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
#include <iostream>
#include <memory>
#include <unistd.h>
struct State{
int result;
};
int computeResult(State &s)
{
std::cout << "p1" << std::endl;
int time = 30;
sleep(time);
s.result = time;
return 1;
}
boost::future<int> doThing() {
std::cout << "p2" << std::endl;
State s;
boost::future<int> fut = boost::async(boost::bind(computeResult, s));
return fut.then(
[&](auto&&) { return s.result; }); //OOPS
}
int main()
{
std::cout << "p3" << std::endl;
auto fut = doThing();
std::cout << "result:" << fut.get() << std::endl;
}
// compile: g++ main.cpp -lboost_thread
// output:
/*
p3
p2
result:p1
21978
*/
寫法一問題:State s;的聲明周期:其超出doThing作用域範圍消亡時,computeResult仍可能在訪問或使用這個變數,此時引用懸空,引發程式崩潰,即當computeResult線程試圖訪問或修改已經被銷毀的State
對象時,會發生未定義的行為。在這種情況下,嘗試從返回的future
對象中獲取結果可能會導致不可預測的結果或程式崩潰。
寫法二問題:未獲取到正確的值,因為s
是局部的,並且當doThing
返回時會被銷毀,所以嘗試訪問它的result
成員是未定義的行為(標記為"OOPS")
解決:使用智能指針shared_ptr管理
#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
#include <iostream>
#include <memory>
#include <unistd.h>
struct State{
int result;
};
int computeResult(std::shared_ptr<State> s)
{
std::cout << "p1" << std::endl;
int time = 30;
sleep(time);
(*s).result = time;
return 1;
}
boost::future<int> doThing() {
std::cout << "p2" << std::endl;
std::shared_ptr<State> s = std::make_shared<State>();
boost::future<int> fut = boost::async(boost::bind(computeResult, s));
return fut.then(
[&](auto&&) { return (*s).result; });
}
int main()
{
std::cout << "p3" << std::endl;
auto fut = doThing();
std::cout << "result:" << fut.get() << std::endl;
}
// compile: g++ main.cpp -lboost_thread
// output:
/*
p3
p2
result:p1
30
*/
④使用結構化併發:cppcoro庫---一個用於C++20的協程庫,它提供了一個輕量級和易於使用的協程支持
未實際編譯
cppcoro::task<> computeResult(State & s);
cppcoro::task<int> doThing() {
State s;
co_await computeResult(s);
co_return s.result;
}
本文來自博客園,作者:circlelll,轉載請註明原文鏈接:https://www.cnblogs.com/circlelll/p/17863575.html