您的位置:首页 > 编程语言 > C语言/C++

新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 (译者注).

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: