c++并发编程实战(2)
2013-11-27 20:11
507 查看
2.管理线程
2.1 线程基本操作
2.1.1 启动一个线程
2-1void do_some_work(); std::thread my_thread(do_some_work);
在hello world那个例子中,我们启动了一个线程。这是最简单的一种情况,线程体是一个返回void的无入口参数的函数。下面我们来看一个稍复杂的例子。
2-2
class background_task { public: void operator()() const { do_something(); do_something_else(); } }; background_task f; std::thread my_thread(f);
是的,在这里可以用一个functor来替代简单函数。当然也可以用lambda表达式:
2-3
std::thread my_thread([]( do_something(); do_something_else(); });
在线程启动后,如果要等等子线程结束,应该调用join,如果不需要等待则调用detach。在主线程结束时会调用my_thread的析构函数,这样在析构函数中会调用std::terminate将子线程杀死。
除了生命周期问题,在访问局部变量时也要注意。
2-4
struct func { int& i; func(int& i_):i(i_){}; void operator()() { for(unsigned j=0;j<1000000;++j) { do_something(i); <--1 } } }; void oops() { int some_local_state=0; func my_func(some_local_state); std::thread my_thread(my_func); my_thread.detach(); <--2 }
局部变量somelocalstate的引用被传入func的成员变量i中。主线程在<--2处detach了子线程,主线程结束后,子线程继续执行,这时语句<--1访问了一个已经被释放的变量。
2.1.2 等待线程结束
正如前边的例子,在thread的实例上调用join()会使调用者等待线程结束。joinable()会返回一个bool,join()和detach()都须在一个joinable的线程实例上调用。同样调用这两个函数之后joinable()会返回false。所以join()和detach()都只能调用一次。2.1.3 处理异常情况
通常detach会在线程启动后立即调用,但调用join()之前主线程可能会做一些别的事情。如果在调用join()之前主线程抛出异常,子线程可能会出现2-4中的问题。所以我们需要处理异常问题2-5
struct func; void f() { int some_local_state=0; func my_func(some_local_state); std::thread t(my_func); try { do_something_in_current_thread(); } catch(...) { t.join(); <--1 throw; } t.join(); <--2 }
在dosomethingincurrentthread中出现异常时会由<--2来处理,没有异常时会由1来处理。看到这大概java程序员会想念finally了。在此我们可以使用一个名为RAII(Resource Acquisition Is Initialization)的常用方法,也就是平常用的各种guard。
2-6
class thread_guard { std::thread& t; public: explicit thread_guard(std::thread& t_):t(t_){} ~thread_guard() { if(t.joinable()) <--1 { t.join(); <--2 } } thread_guard(thread_guard const&)=delete; <--3 thread_guard& operator=(thread_guard const&)=delete; }; struct func; void f() { int some_local_state=0; func my_func(some_local_state); std::thread t(my_func); thread_guard g(t); do_something_in_current_thread(); } <--4
当程序运行到<--4在函数退出前调用thread_guard的析构,从而执行<--2的join()。因为join只能的线程是joinable时调用,所以在<--1处用joinable()进行一下保护。<--3是为了防止错误的调用拷贝构造和赋值操作。这在11x之前是通过写一个private的空函数来实现的。
2.1.4 启动一个后台线程
对一个线程对象调用detach()之后它就成为后台线程了,之后joinable()会返回false。也不能再对它调用join()或detach()了。2.2 向线程函数传递参数
void f(int i,std::string const& s); std::thread t(f,3,”hello”);
thread的构造函数支持可变参数,用来向线程函数传递参数,用法和std::bind是一样的。根据需要不同,有时需要向线程传递值,有时需要传引用。例如,在线程内修改变量内容,在主线程去读取,就需要传引用。还有一种情况需要将变量move到线程中,尤其当数据较大时。
2.2.1 传值
错误void f(int i,std::string const& s); void oops(int some_param) { char buffer[1024]; sprintf(buffer, "%i",some_param); std::thread t(f,3,buffer); t.detach(); }
oops()结束后,buffer已经被释放了,但f()有可能继续访问。
正确
void f(int i,std::string const& s); void not_oops(int some_param) { char buffer[1024]; sprintf(buffer,"%i",some_param); std::thread t(f,3,std::string(buffer)); <--1 t.detach(); }
<--1 在参数传递给thread()构造函数前先构造一个临时对象。
2.2.2 传引用
错误void update_data_for_widget(widget_id w,widget_data& data); void oops_again(widget_id w) { widget_data data; std::thread t(update_data_for_widget,w,data); <--1 display_status(); t.join(); process_widget_data(data); <--2 }
因为在<--1处是将data的值传入,所以updatedatafor_widget()中的修改不能返回。修改的方法是
std::thread t(update_data_for_widget,w,std::ref(data));
2.2.3 move
在11x中,通过编写右值引用的拷贝构造和赋值操作可以对右值支持move语义。对左值可以用如下方法。void process_big_object(std::unique_ptr<big_object>); std::unique_ptr<big_object> p(new big_object); p->prepare_data(42); std::thread t(process_big_object,std::move(p));
2.3在thread实例间传递线程所有权
象ifstream和unique_ptr一样,thread是moveable而不是copyable的。void some_function(); void some_other_function(); std::thread t1(some_function); <--1 std::thread t2=std::move(t1); <--2 t1=std::thread(some_other_function); <--3 std::thread t3; <--4 t3=std::move(t2); <--5 t1=std::move(t3); <--6
<--1 创建一个新线程执行函数some_function,并与实例t1关联
<--2 所有权传递给t2,这时t1.get_id()会返回0。
<--3 t1关联到新线程。这里不需要move()因为等号右边是一个右值。在这里t1=t2,t1={t2}都是不可以的,做为一个非copyable的类,thread将基于左值引用的拷贝构造和赋值都delete了。
<--4 thread的缺省构造表示没有关联任何线程
<--5 将线程所有权move给t3
<--6 和<--5的区别是,这时t1不是一个空的实例,会调用std::terminate()将someotherfunction线程终止。
返回一个thread实例
std::thread f() { void some_function(); return std::thread(some_function); //返回右值 } std::thread g() { void some_other_function(int); std::thread t(some_other_function,42); return t; }
thread做为函数参数
void f(std::thread t); void g() { void some_function(); f(std::thread(some_function)); //直接使用右值 std::thread t(some_function); f(std::move(t)); //左值使用move() }
scope_thread
class scoped_thread { std::thread t; public: explicit scoped_thread(std::thread t_): t(std::move(t_)) { if(!t.joinable()) throw std::logic_error(“No thread”); } ~scoped_thread(){ t.join(); } scoped_thread(scoped_thread const&)=delete; scoped_thread& operator=(scoped_thread const&)=delete; }; struct func; void f() { int some_local_state; scoped_thread t(std::thread(func(some_local_state))); do_something_in_current_thread(); }
2-6的另一种写法。
启动多线程并等待它们结束
void do_work(unsigned id); void f() { std::vector<std::thread> threads; for(unsigned i=0;i<20;++i) { threads.push_back(std::thread(do_work,i)); } std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join)); }
2.4 运行时选择线程数目
std::thread::hardware_concurrency()对同时启动几个线程会给出一个建议的数字,通常是多核机器上core的数目。如i7会返回8。下面给出一个例子
template<typename Iterator,typename T> struct accumulate_block { void operator()(Iterator first,Iterator last,T& result) { result=std::accumulate(first,last,result); } }; template<typename Iterator,typename T> T parallel_accumulate(Iterator first,Iterator last,T init) { unsigned long const length=std::distance(first,last); if(!length) return init; unsigned long const min_per_thread=25; unsigned long const max_threads=(length+min_per_thread-1)/min_per_thread; unsigned long const hardware_threads=std::thread::hardware_concurrency(); unsigned long const num_threads=std::min(hardware_threads!=0?hardware_threads:2,max_threads); unsigned long const block_size=length/num_threads; std::vector<T> results(num_threads); std::vector<std::thread> threads(num_threads-1); Iterator block_start=first; for(unsigned long i=0;i<(num_threads-1);++i) a224 { Iterator block_end=block_start; std::advance(block_end,block_size); threads[i]=std::thread(accumulate_block<Iterator,T>(), block_start,block_end,std::ref(results[i])); block_start=block_end; } accumulate_block<Iterator,T>()(block_start,last,results[num_threads-1]); std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join)); return std::accumulate(results.begin(),results.end(),init); }
多线程求和函数每个线程至少处理25个对象的累加,线程数目不超过hardware_concurrency()的建议值。
2.5标识线程
有两种方法返回线程id
thread.get_id() 返回和thread实例相关的线程id this_thread.get_id() 返回当前线程id
相关文章推荐
- c++并发编程实战(C++11)pdf 高清
- c++并发编程实战(1)
- C++并发编程实战(读书笔记)——C++内存模型不好理解;无锁数据结构?但是等待不就是被锁住了吗??
- c++并发编程实战(3)
- C++并发编程实战chapter1你好,C++的并发世界--笔记1--任务并行和数据并行
- C++并发实战15:函数式编程
- Java并发编程实战--协作对象间的死锁与开放调用
- 【Java并发编程实战】—–synchronized
- 【Java并发编程实战】-----“J.U.C”:Exchanger
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析
- 【Java并发编程实战】—–synchronized
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析
- 【Java并发编程实战】-----“J.U.C”:Exchanger
- 并发编程实战 1.9. 线程组 - ThreadGroup
- linux fork多进程并发服务器模型之C/C++代码实战
- C++并发编程2——为共享数据加锁(四)
- 并发编程实战学习笔记(三)——基础构建模块
- C++实战之OpenCL 并行优化编程从零学起系列文章
- JAVA项目实战,项目架构,高并发,分布式,微服务架构,微信支付,支付宝支付,理财系统,并发编程视频
- c++并发编程 - Boost Thread