新C++标准:C++0x教程(四):面向所有开发者的特性(下)
2013-11-26 16:59
465 查看
译者:yurunsun@gmail.com新浪微博@孙雨润新浪博客CSDN博客日期:2012年11月14日
原作:Scott Meyers
这些是Scott Meyers培训教程《新C++标准:C++0x教程》的官方笔记,培训课程的描述请见 http://www.aristeia.com/C++0x.html,版权信息请见 http://aristeia.com/Licensing/licensing.html.
漏洞和建议请发送邮件到
smeyers@aristeia.com. 翻译错误请发送邮件给yurunsun@gmail.com (译者注).
线程作为独立运行单元
条件变量用于“阻塞直到为true”(类似自旋锁)
线程局部变量用于线程内专用数据
相应头文件:
调用过程中这些数据不会被销毁
只有函数内能改变这些数据的值
例如:
调用过程中:
但是异步调用无法保证这种冻结状态:
调用过程中:
9.3.1
上例中会copy
上例中闭包copy了
9.3.3
上例
注意这里会阻塞调用
如果真的想传
还存在一个
9.4 异步调用
同裸调
9.4.1
调用方式
9.4.2
2010年11月之前
对于deferred方式的async, 根据N2973的lazy evaluation法则,任务会在调用
9.4.3
结果通过
对
9.4.4
注意可能会超时,必须设置超时时间。
与
对于unshared futures, 当你知道任务以异步方式运行的时候
9.4.5
当调用者对返回值不感兴趣(但可能对异常感兴趣)的时候。
对于
注意:截止到gcc4.8.1, 仍然存在对std::thread支持的bug, 需要按照如下方式构建qmake. 对Makefile以此类推:
9.5.1 四种
9.5.2
9.5.3
9.5.4
死锁问题很容易出现在试图获取两个资源的时候。
如果上例中
输出如下:
输出如下:
用
`std::atomic
查询硬件支持的线程数
mutex与它的RAII对象
解决死锁问题
条件变量
线程局部数据
新的for循环写法
unicode支持
统一初始化
λ
模板的别名
并发支持
如果这篇文章对您有帮助,请到CSDN博客留言;
转载请注明:来自雨润的技术博客 http://blog.csdn.net/sunyurun
原作:Scott Meyers
这些是Scott Meyers培训教程《新C++标准:C++0x教程》的官方笔记,培训课程的描述请见 http://www.aristeia.com/C++0x.html,版权信息请见 http://aristeia.com/Licensing/licensing.html.
漏洞和建议请发送邮件到
smeyers@aristeia.com. 翻译错误请发送邮件给yurunsun@gmail.com (译者注).
9. 并行编程
基本组件:线程作为独立运行单元
std::async和
futures执行异步调用
mutex用来控制共享数据
条件变量用于“阻塞直到为true”(类似自旋锁)
线程局部变量用于线程内专用数据
相应头文件:
#include <thread> #include <mutex> #include <condition_variable> #include <future>
9.1 线程
std::thread将能调用的对象作为参数,并异步执行:
void doThis(); class Widget { public: void operator()() const; void normalize(long double, int, std::vector<float>); … }; std::thread t1(doThis); // run function asynch. Widget w; … std::thread t2(w); // “run” function object asynch.
lambda可以用作可调用对象:
long double ld; int x; std::thread t3([=]{ w.normalize(ld, x, { 1, 2, 3 }); }); // “run” closure asynch.
9.2 数据生存期的考虑
在单线程中的函数调用时,外部数据是冻结状态,意思是:调用过程中这些数据不会被销毁
只有函数内能改变这些数据的值
例如:
int x, y, z; Widget *pw; … f(x, y); // call in ST system
调用过程中:
x, y, z, pw都会存在,对于
pw,除非函数内将其销毁;他们的值只能在函数内被改变。
但是异步调用无法保证这种冻结状态:
int x, y, z; Widget *pw; … call f(x, y) asynchronously (i.e., on a new thread);
调用过程中:
x, y, z, pw可能超过生存期,
pw可能被销毁;他们的值可能被改变。通过以传值方式传递参数,可以使函数内部对参数的访问不受影响:
void f(int xParam); // function to call asynchronously { int x; … std::thread t1([&]{ f(x); }); // risky! closure holds a ref to x std::thread t2([=]{ f(x); }); // okay, closure holds a copy of x … } // x destroyed
9.3 创建线程时的参数传递
9.3.1 std::thread
构造函数
std::thread有一个不定参数的构造函数,将任何参数拷贝一次。
void f(int xVal, const Widget& wVal); int x; Widget w; … std::thread t(f, x, w); // invoke copy of f on copies of x, w
上例中会copy
f, x, w,确保异步回调时变量存在。在函数
f内部,
xVal和
wVal是
w的拷贝的引用,而不是
w的引用。
9.3.2 闭包中传值:
void f(int xVal, const Widget& wVal); int x; Widget w; … std::thread t([=]{ f(x, w); }); // invoke copy of f on copies of x, w
上例中闭包copy了
x, w,然后
std::thread的构造函数会将闭包copy一份,确保异步调用时仍然存在,在
f内部,
wVal是
w的拷贝的引用,而不是
w的引用。
9.3.3 std::bind
void f(int xVal, const Widget& wVal); int x; Widget w; … std::thread t(std::bind(f, x, w)); // invoke f with copies of x, w
上例
bind返回的对象包含
x, w的拷贝,
std::thread会拷贝这个对象,确保异步调用时仍然存在,在
f内部,
wVal是
w的拷贝的引用,而不是
w的引用。
lambda表达式推荐与
bind结合使用,不仅易于理解,而且效率更高。
9.3.4 确保变量的生存期
一种方法是延后局部变量的销毁时间,直到异步调用结束:void f(int xVal, const Widget& wVal); // as before { int x; Widget w; … std::thread t([&]{ f(x, w); }); // wVal really refers to w … t.join(); // destroy w only after t finishes }
注意这里会阻塞调用
f的线程
9.3.5 混用传值与传引用
void f(int xVal, int yVal, int zVal, Widget& wVal);
如果真的想传
wVal的引用,而不是拷贝的引用,该怎么做呢?
lambda闭包
{ Widget w; int x, y, z; … std::thread t([=, &w]{ f(x, y, z, w); }); // pass copies of x, y, z; pass w by reference }
std::bind和
std::thread构造函数,使用
std::ref
void f(int xVal, int yVal, int zVal, Widget& wVal); // as before { static Widget w; int x, y, z; … std::thread t1(f, x, y, z, std::ref(w)); // pass copies of std::thread t2(std::bind(f, x, y, z, std::ref(w))); // x, y, z; pass w by reference }
还存在一个
std::cref表示const引用
9.4 异步调用 std:async
+ std::future
同裸调std::thread相比,
std::async+
std::future的方式能够获取异常与返回值,另外调用方式与普通函数更类似。注意与
async相关的概念还有
promise和
packaged_task,这里暂时没有设计。
9.4.1 async
执行异步调用, future
取回结果或者错误
调用方式double bestValue(int x, int y); // something callable and time-costing std::future<double> f = // run λ asynch.; std::async( []{ return bestValue(10, 20); } ); // get future for it do other work … double val = f.get(); // get result (or exception) from λ
std::async可能会使用线程池来实现
9.4.2 async
加载策略
std::launch::async: 在新线程执行
std::launch::deferred: 在
get或
waiting的时候执行
auto f = std::async(std::launch::deferred, []{ return bestValue(10, 20); }); … auto val = f.get(); // run λ synchronously here
2010年11月之前
std::launch::deferred曾叫做
std::launch::sync
对于deferred方式的async, 根据N2973的lazy evaluation法则,任务会在调用
get/wait时执行。如果调用
wait_for/wait_until,会立即返回
std::future_status_deferred
9.4.3 futures
std::future<T>: 结果可能只能获取一次,可以move但不能copy
std::shared_future<T>: 结果可以多次获取,当多个线程需要同一个
future的结果时比较合适,可以move/copy, 可以通过
std::future<T>创建,但是这会导致所有权被剥夺。
std::async和
std::promise都返回
std::future,在2009年11月之前
std::future叫做
std::unique_future。
结果通过
get获取,调用时可能会发生阻塞,然后grab结果。对于
future,grab的意思是:如果能move则move,否则copy;对于
shared_future<T>,grab的意思是获取对方的引用;结果可能是一个异常。
对
std::future的第二次之后的get会导致
undefined的结果,而对
std::shared_future会得到相同的结果。
9.4.4 wait
wait会阻塞,知道有结果返回。
std::future<double> f = std::async([]{ return bestValue(10, 20); }); … f.wait();
注意可能会超时,必须设置超时时间。
与
std::launch::async方式匹配使用是比较常见的方式:
std::future<double> f = std::async(std::launch::async, []{ return bestValue(10, 20); }); … while (f.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { // if result of λ isn’t ready, do more work ... } double val = f.get(); // grab result
对于unshared futures, 当你知道任务以异步方式运行的时候
wait_for最有用,因为调用
wait_for不会超时,而仅仅是同步执行,一直阻塞至任务完成。。还要注意的是这里的
wait_for不支持等待多个
future中的一个,而windows下的
WaitForMultipleObjects能做到这一点。
9.4.5 void futures
当调用者对返回值不感兴趣(但可能对异常感兴趣)的时候。void initDataStructs(int defValue); void initGUI(); std::future<void> f1 = std::async( []{ initDataStructs(-1); } ); std::future<void> f2 = std::async( []{ initGUI(); } ); … // init everything else f1.get(); // wait for asynch. inits. to f2.get(); // finish (and get exceptions,if any) // proceed with the program
对于
void futures使用wait
还是get
,取决于是否需要超时处理(只有wait
支持超时)以及是否需要获取异常(get
可以做到)。wait
还可以被当成一个信号机制,也就是用来告诉其他线程,他们等待的一个操作已经完成了。此外,wait
可以在任何时候强制执行以deferred`加载的一步函数。
注意:截止到gcc4.8.1, 仍然存在对std::thread支持的bug, 需要按照如下方式构建qmake. 对Makefile以此类推:
QMAKE_CXXFLAGS += -std=c++0x QMAKE_LFLAGS += -Wl,--no-as-needed -pthread SOURCES += main.cpp
9.4.6 代码示例
using namespace std; int main(int argc, char* argv[]) { cout << "[Main Thread ID] " << this_thread::get_id() << endl; vector<future<void>> futures; for (size_t i = 0; i < 10; ++i) { auto fut = async(launch::async, []{ this_thread::sleep_for(chrono::seconds(1)); cout << this_thread::get_id() << " "; }); futures.push_back(move(fut)); } for (future<void>& fut : futures) { fut.wait(); } }
9.5 Mutexes
9.5.1 四种mutex
std::mutex不循环、不支持超时
std::timied_mutex不循环、支持超时
std::recursive_mutex循环、不支持超时
std::recursive_timed_mutex循环、支持超时
mutex既不能copy也不能move
9.5.2 mutex
的RAII类:std::lock_guard
std::mutex m; { std::lock_guard<std::mutex> lock(m); }`lock_guard`既不能copy也不能move[/code]
9.5.3 mutex
的RAII类:std::unique_lock
using tm_t = std::timed_mutex; rm_t m; { std::unique_lock<tm_t> lock(m, std::defer_lock); if (lock.try_lock_for(std::chrono::microseconds(10))) { /// critical section } else { /// handle timeout case } if (lock) { /// critical section } else { /// m is not locked } }`unique_lock`可以调用`lock/unlock`,可以move,支持timeout类型的mutex. 上例中试图在10ms中获取锁,如果成功或者超时则返回。如果超时,后续lock会被隐式转换为false, 如果在10ms中成功获得锁,后续lock会被隐式转换为true.`try_lock_for()`中传入的参数<=0 相当于调用 `try_lock`,能够直接返回成功还是失败。[/code]
9.5.4 mutex
的RAII类:std::lock
死锁问题很容易出现在试图获取两个资源的时候。int weight, value; std::mutex wt_mux, val_mux; { // Thread 1 std::lock_guard<std::mutex> wt_lock(wt_mux); // wt 1st std::lock_guard<std::mutex> val_lock(val_mux); // val 2nd work with weight and value // critical section } { // Thread 2 std::lock_guard<std::mutex> val_lock(val_mux); // val 1st std::lock_guard<std::mutex> wt_lock(wt_mux); // wt 2nd work with weight and value // critical section }
std::lock能够解决这个问题:
{ // Thread 1 std::unique_lock<std::mutex> wt_lock(wt_mux, std::defer_lock); std::unique_lock<std::mutex> val_lock(val_mux, std::defer_lock); std::lock(wt_lock, val_lock); // get mutexes w/o } { // Thread 2 std::unique_lock<std::mutex> val_lock(val_mux, std::defer_lock); std::unique_lock<std::mutex> wt_lock(wt_mux, std::defer_lock); std::lock(val_lock, wt_lock); // get mutexes w/o }
如果上例中
val_lock/wt_lock已经被锁,那么会抛出异常。如果调用的参数是
mutex而且已经被锁了,那么这种行为是undefined
9.6 条件变量
std::mutex m; std::condition_variable cv; std::string data; bool ready = false; bool processed = false; void worker_thread() { // Wait until main() sends data { std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{return ready;}); } std::cout << "[2] Worker thread is processing data\n"; data += " after processing"; // Send data back to main() { std::lock_guard<std::mutex> lk(m); processed = true; std::cout << "[3] Worker thread signals data processing completed\n"; } cv.notify_one(); } int main() { std::thread worker(worker_thread); data = "Example data"; // send data to the worker thread { std::lock_guard<std::mutex> lk(m); ready = true; std::cout << "[1] main() signals data ready for processing\n"; } cv.notify_one(); // wait for the worker { std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{return processed;}); } std::cout << "[4] Back in main(), data = " << data << '\n'; worker.join(); }
输出如下:
[1] main() signals data ready for processing [2] Worker thread is processing data [3] Worker thread signals data processing completed [4] Back in main(), data = Example data after processing
9.7 线程局部数据
thread_local是C++11中新引进的关键词,地位与
static,
extern相同。
thread_local unsigned int rage = 1; std::mutex cout_mutex; void increase_rage(const std::string& thread_name) { ++rage; std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "Rage counter for " << thread_name << ": " << rage << '\n'; } int main() { std::thread a(increase_rage, "a"), b(increase_rage, "b"); { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "Rage counter for main: " << rage << '\n'; } a.join(); b.join(); return 0; }
输出如下:
Rage counter for a: 2 Rage counter for main: 1 Rage counter for b: 2
用
thread_local声明的变量在每个线程都有一份实例。
9.8 其他一些并发的支持
对象的线程安全初始化std::call_once,
std::once_flag
detach
std::packaged_task
`std::atomic
yield,
sleep
查询硬件支持的线程数
9.9 并发支持的总结
join/detach
async/future
mutex与它的RAII对象
解决死锁问题
条件变量
线程局部数据
10. 面向所有开发者的特性总结
模板>>
auto变量
新的for循环写法
nullptr
unicode支持
统一初始化
λ
模板的别名
并发支持
如果这篇文章对您有帮助,请到CSDN博客留言;
转载请注明:来自雨润的技术博客 http://blog.csdn.net/sunyurun
相关文章推荐
- 新C++标准:C++0x教程(三):面向所有开发者的特性(中)
- 新C++标准:C++0x教程(二):面向所有开发者的特性(上)
- 新C++标准:C++0x教程(三):面向所有开发者的特性(中)
- 新C++标准:C++0x教程(二):面向所有开发者的特性(上)
- 新C++标准:C++0x教程(一):导论
- C++17 标准正式发布:开发者可更简单地编写和维护代码
- C++在非面向对象方面的一些特性(基本)
- C++开发者都应该使用的10个C++11特性
- c++和java语言特性的不同(一个c++开发者眼中的java)
- C++开发者都应该使用的10个C++11特性
- 7 个面向Web开发者的实用CSS3教程推荐
- C++开发者都应该使用的10个C++11特性
- 【C++标准】之 C++11——auto特性
- javascript面向对象程序设计高级特性经典教程(值得收藏)
- C++开发者都应该使用的10个C++11特性
- C语言实现C++中面向对象特性
- C++开发者都应该使用的10个C++11特性 转
- 每个C++开发者都应该使用的十个C++11特性
- CocosStudio v2.x版本教程——“回调特性”(C++语言版)
- C++基础学习教程(七)----类编写及类的两个特性解析--->多态&继承