C++‘s Most Vexing Parse
凡是长的像函数声明的,都是函数声明。
常常出现在类的构造中使用匿名对象的时候。
string mystr(); vector<int> my_ivec(); int b(int()); // 这些都是函数声明
解决办法:
将匿名对象使用括号扩起来,作为一个整体
使用C++11的初始化语法 {}
启动线程
#include <iostream> #include <thread> using std::cout; using std::thread; class background_task { public: void operator()() { do_something(); do_something_else(); } private: void do_something(); void do_something_else(); }; void background_task::do_something() { cout<<__FUNCTION__<<": exec...\n"; } void background_task::do_something_else() { cout<<__FUNCTION__<<": exec...\n"; } int main() { // background_task f; // thread my_thread(f); // // my_thread.join(); // error example // my_thread has no member function of join // C++'s most vexing parse // it is translated as // thread my_thread( background_task (*pn)() ); // it's a function declaration. //thread my_thread(background_task()); //my_thread.join(); // thread my_thread((background_task())); // my_thread.join(); thread my_thread{background_task()}; my_thread.join(); return 0; }
也可以传递lambda表达式来启动线程
#include <iostream> #include <thread> using std::cout; using std::thread; void do_something() { cout<<__FUNCTION__<<": exec ...\n"; } void do_something_else() { cout<<__FUNCTION__<<": exec ...\n"; } int main() { thread my_thread([] { do_something(); do_something_else(); }); my_thread.join(); return 0; }
启动线程后,需要明确是要等待线程结束(加入式),还是让其自主运行(分离式)。需要在thread对象销毁前决定。
#include <iostream> #include <thread> #include <string.h> using std::cout; using std::thread; void do_something(int& pi) { cout<<pi<<"\t"; } struct func { int& pi; public: func(int& _pi): pi(_pi) {} ~func() { } void operator() () { int j = 0; for(;j<100000;j++) { do_something(pi); } } }; void oops(){ int local_variable = 0; thread my_thread{func(local_variable)}; //my_thread.detach(); my_thread.join(); } int main() { oops(); for(int i = 0; i < 1000; i++) { int* p = new int[100](); memset(p,50,100* sizeof(int)); delete[] p; } return 0; }
这段代码中,线程函数对象中的引用(或指针)指向的是oops()函数中的局部变量,当使用detach时,oops()函数结束时,线程函数还在运行中,导致在对象中使用了非法的引用,导致数据上的错误。
如果一个可调用对象作为线程函数,对于对象中包含指针或引用时,需要特别的谨慎。
线程的异常安全问题
在有异常的情况下,正确的等待线程函数的退出。 在函数f()中,因为调用的div_s会抛出异常,如果f()中进行捕获过滤,则可以通过最后的join()退出线程函数,如果f()中不进行处理,而是像上一级抛出异常,则需要先退出线程函数,即在throw 之前调用join()函数。
#include <iostream> #include <thread> #include <string.h> #include <unistd.h> using std::thread; using std::cout; int div_s(int denum, int num){ cout<<denum<<" / "<<num<<"\n"; if(num) { return denum / num; } throw num; } void do_something(int& i) { for(int j = 0; j < 100000; j++) { if(i) { cout<<"err happend.\n"; } } cout<<"bye bye.\n"; } struct func{ int& i; func(int& i_):i(i_) {} void operator() () { do_something(i); } }; void f() { int some_local_state = 0; thread my_thread{func(some_local_state)}; try { div_s(10,2); div_s(8,4); div_s(5,0); div_s(3,1); } catch(...) { cout<<"catch exception.\n"; //如果这里需要往上抛出异常,则需要先调用join,等待线程函数退出,然后再抛出异常 my_thread.join(); throw; } cout<<"here!\n"; my_thread.join(); } int main() { try{ f(); } catch(...) { cout<<"catch exception in main\n"; } for(int i = 0; i < 1000; i++) { int* p = new int[100]; memset(p,0,sizeof(p)); } return 0; }
另一种思路是使用RAII(Resource Acquisition is Initialization 资源获取即初始化方式)
注意将封装类的赋值构造函数和赋值运算符重载给删除。
#include <iostream> #include <thread> #include <string.h> #include <unistd.h> using std::thread; using std::cout; int div_s(int denum, int num){ cout<<denum<<" / "<<num<<"\n"; if(num) { return denum / num; } throw num; } void do_something(int& i) { for(int j = 0; j < 100000; j++) { if(i) { cout<<"err happend.\n"; } } cout<<"bye bye.\n"; } struct func{ int& i; func(int& i_):i(i_) {} void operator() () { do_something(i); } }; class thread_guard { public: explicit thread_guard(thread& t_):t(t_) {} ~thread_guard() { if(t.joinable()) { t.join(); } } thread_guard(const thread_guard& ) = delete; thread_guard& operator= (const thread_guard& )= delete; private: thread& t; }; void do_something_in_current_thread() { div_s(10,2); div_s(8,4); div_s(5,0); div_s(3,1); } void f() { int some_local_state = 0; thread my_thread{func(some_local_state)}; thread_guard g(my_thread); do_something_in_current_thread(); } int main() { try{ f(); } catch(...) { cout<<"catch exception in main\n"; } for(int i = 0; i < 1000; i++) { int* p = new int[100]; memset(p,0,sizeof(p)); } return 0; }
通常称分离线程为守护线程(daemon threads), 没有任何显示的用户接口,并在后台运行的线程。长时间运行,比如在后台监视文件系统,对缓存进行清理等工作。发后即忘(fire and forget).
检查是否可以调用detach 是调用joinable()函数,返回true就可以调用detach.
向线程函数传递参数时,默认参数要拷贝到线程独立内存中,即使参数是引用的形式。
线程函数参数是引用时,必须加const修饰符修饰,要想使参数是引用,并达到修改的效果,传递给线程函数参数时,需要使用std::ref()进行传递参数。
#include <iostream> #include <thread> #include <string> using namespace std; void f(int i, string const& s) { cout<<i<<"\t"<<s<<"\n"; } void oops(int some_param) { char buffer[1024]; sprintf(buffer,"%i",some_param); thread t(f,3,string(buffer)); //thread t(f,3,buffer); // 有问题,因为buffer是局部变量,detach后oops可能在线程函数执行完成前结束,可以使用string提前转化成string对象 t.detach(); } struct Test_data { int x; int y; int z; }; void update_data(struct Test_data& data) { data.x += 5; data.y += 5; data.z += 5; } void oops_again() { struct Test_data data; data.x = 0; data.y = 0; data.z = 0; thread t(update_data,std::ref(data)); t.join(); cout<<"after update data = \n"<<data.x<<"\t" <<data.y<<"\t"<<data.z<<"\n"; } int main() { oops(5); thread t(f,3,"hello"); t.join(); oops_again(); return 0; }
指针可以传递,没有问题,也能达到修改的目的。
线程作为函数的返回值
#include <iostream> #include <thread> using std::thread; using std::cout; void f1() { cout<<__FUNCTION__<<" called.\n"; } void f2(int num) { cout<<__FUNCTION__<<" called, num = "<<num<<"\n"; } thread f() { return thread(f1); } thread g() { return thread (f2,45); } int main() { thread t = f(); t.join(); thread t1 = g(); t1.join(); return 0; }
线程作为函数参数
#include <iostream> #include <thread> #include <unistd.h> using std::thread; using std::cout; void work() { cout<<__FUNCTION__<<" called\n"; int cnt = 0; while(cnt ++ < 5) { sleep(1); printf("cnt = %d\n",cnt); } } void f(thread t) { cout<<__FUNCTION__<<" called\n"; if(t.joinable()) t.join(); } void g() { f(thread(work)); thread t (work); f(std::move(t)); } int main() { g(); return 0; }
多线程并行计算模型
#include <iostream> #include <algorithm> #include <thread> #include <future> #include <vector> using std::cout; using std::thread; using std::vector; using std::promise; using std::future; int nums[100]; void do_work(unsigned int id, std::promise<int>& proObj) { int sum = 0; for(int i = id * 10 ; i <= id * 10 + 9; i++) { sum += nums[i]; } proObj.set_value(sum); } void f() { vector<thread> threads; vector<promise<int> > proObjs(10); vector<future<int> > futureObjs(10); for(unsigned i = 0; i < 10; i ++) { futureObjs[i] = proObjs[i].get_future(); threads.push_back(thread(do_work,i,std::ref(proObjs[i]))); } int sum = 0; for(int i = 0; i < 10; i ++) { sum += futureObjs[i].get(); } for_each(threads.begin(),threads.end(), std::mem_fn(&std::thread::join)); printf("sum = %d\n",sum); } int main() { for(int i = 0; i < 100; i ++) { nums[i] = i + 1; } f(); return 0; }
多核系统中CPU核芯数量
#include <iostream> #include <thread> using namespace std; int main() { int cnt = std::thread::hardware_concurrency(); cout<<cnt<<endl; return 0; } # 总核数 = 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数 # 查看物理CPU个数 cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l # 查看每个物理CPU中core的个数(即核数) cat /proc/cpuinfo| grep "cpu cores"| uniq # 查看逻辑CPU的个数 cat /proc/cpuinfo| grep "processor"| wc -l 查看CPU信息(型号) cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c
获取线程id std::thread::id类型
thread对象的get_id()函数
std::this_thread::get_id()函数
运行时决定线程数目
#include <iostream> #include <thread> #include <numeric> #include <algorithm> #include <vector> using namespace std; template <typename Iterator, typename T> struct accumulate_block { void operator() (Iterator first, Iterator last, T& result) { result = accumulate(first,last,result); } }; template <typename Iterator, typename T> T parallel_accumulate(Iterator first, Iterator last, T init) { const unsigned long length = std::distance(first,last); if(!length) return init; const unsigned long min_per_thread = 25; const unsigned long max_threads = (length + min_per_thread -1) / min_per_thread; const unsigned long hardware_threads = std::thread::hardware_concurrency(); const unsigned long num_threads = std::min(hardware_threads ? hardware_threads : 2 , max_threads); const unsigned long block_size = length / num_threads; vector<T> results(num_threads); vector<thread> threads(num_threads -1); Iterator block_start = first; for(unsigned long i = 0; i < num_threads -1 ; ++i) { 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(&thread::join)); return std::accumulate(results.begin(),results.end(),init); } int main() { vector<int> myVec(100); for(int i = 0; i < 100; ++i) myVec[i] = (i+1); int ret = parallel_accumulate(myVec.begin(),myVec.end(),0); cout<<"ret = "<<ret<<" \n"; return 0; }
C++标准库为互斥量提供了一个RAII语法的模板类std::lock_guard,其会在构造的时候提供已锁的互斥量,并在析构的时候进行解锁。
当其中一个成员函数返回的是保护数据的指针或引用时,会破坏对数据的保护。具有访问能力的指针或者引用可以访问(并可能修改)被保护的数据,而不会被互斥锁限制。
切勿将受保护数据的指针或引用传递到互斥锁作用域之外,无论是函数返回值,还是存储在外部可见内存,亦或是以参数的形式传递到用户提供的函数中去。
#include <iostream> #include <mutex> #include <algorithm> #include <list> using namespace std; list<int> some_list; mutex some_mutex; void add_to_list(int new_value) { lock_guard<mutex> guard(some_mutex); some_list.push_back(new_value); } bool list_contains(int value_to_find) { lock_guard<mutex> guard(some_mutex); return find(some_list.begin(),some_list.end(),value_to_find) != some_list.end(); } int main() { return 0; }
避免死锁的一般建议,就是让两个互斥量总是以相同的顺序上锁
#include <iostream> #include <mutex> using namespace std; class some_big_object { }; void swap(some_big_object& lhs, some_big_object& rhs) { } class X { private: some_big_object some_detail; mutex m; public: X(const some_big_object& sd) : some_detail(sd) {} friend void swap(X& lhs, X& rhs) { if(&lhs == &rhs) return; lock(lhs.m,rhs.m); lock_guard<mutex> lock_a(lhs.m,std::adopt_lock); lock_guard<mutex> lock_b(rhs.m,std::adopt_lock); swap(lhs.some_detail,rhs.some_detail); } }; class X2 { private: some_big_object some_detail; mutex m; public: X2(some_big_object& sd) : some_detail(sd) {} friend void swap(X2& lhs, X2& rhs) { if(&lhs == &rhs) return; std::unique_lock<mutex> lock_a(lhs.m,std::defer_lock); std::unique_lock<mutex> lock_b(rhs.m,std::defer_lock); std::lock(lock_a,lock_b); swap(lhs.some_detail,rhs.some_detail); } }; int main() { return 0; }
避免嵌套锁: 当需要获取多个锁,使用一个std::lock来做这件事(对获取锁的操作上锁),避免产生死锁。
使用固定的顺序获取锁
层次锁的封装
#include <iostream> #include <climits> #include <mutex> #include <thread> using namespace std; class hierarchical_mutex { private: mutex internal_mutex; unsigned long const hierarchy_value; unsigned long previous_hierarchy_value; static thread_local unsigned long this_thread_hierarchy_value; void check_for_hierarchy_violation() { if(this_thread_hierarchy_value <= hierarchy_value) { throw std::logic_error("mutex hierarchy violated"); } } void update_hierarchy_value() { previous_hierarchy_value=this_thread_hierarchy_value; this_thread_hierarchy_value = hierarchy_value; } public: explicit hierarchical_mutex(unsigned long value) : hierarchy_value(value) , previous_hierarchy_value(0) {} void lock() { check_for_hierarchy_violation(); internal_mutex.lock(); update_hierarchy_value(); } void unlock() { this_thread_hierarchy_value=previous_hierarchy_value; internal_mutex.unlock(); } bool try_lock() { check_for_hierarchy_violation(); if(!internal_mutex.try_lock()) return false; update_hierarchy_value(); return true; } }; thread_local unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX); hierarchical_mutex high_level_mutex(10000); hierarchical_mutex low_level_mutex(5000); int do_low_level_stuff(){ cout<<__FUNCTION__<<"\n"; return 1; } int low_level_func() { lock_guard<hierarchical_mutex> lk(low_level_mutex); return do_low_level_stuff(); } void high_level_stuff(int some_param) { cout<<__FUNCTION__<<" "<<some_param<<"\n"; } void high_level_func() { lock_guard<hierarchical_mutex> lk(high_level_mutex); high_level_stuff(low_level_func()); } void thread_a() { high_level_func(); } hierarchical_mutex other_mutex(100); void do_other_stuff() { cout<<__FUNCTION__<<"\n"; } void other_stuff() { high_level_func(); do_other_stuff(); } void thread_t() { lock_guard<hierarchical_mutex> lk(other_mutex); other_stuff(); } int main() { thread t(thread_a); t.join(); thread t2(thread_t); t2.join(); return 0; }
使用std::once_flag和std::call_once来实现对构造函数的保护
class X { private: connection_info connection_details; connection_handle connection; std::once_flag connection_init_flag; void open_connection() { connection = connection_manager.open(connection_details); } public: X(connection_info const & connection_details_) : connection_details(connection_details_) {} void send_data(data_packet const & data) { std::call_once(connection_init_flag, &X::open_connection,this); connection.send_data(data); } data_packet receive_data() { std::call_once(connection_init_flag, &X::open_connection,this); return connection.receive_data(); } };
C++的线程库中暂时没有读写锁,boost提供了读写锁
#include <map> #include <string> #include <mutex> #include <boost/thread/shared_mutex.hpp> class dns_entry; class dns_cache { private: std::map<std::string, dns_entry> entries; mutable boost::shared_mutex entry_mutex; public: dns_entry find_entry(std::string const & domain) const { boost::shared_lock<boost::shared_mutex> lk(entry_mutex); std::map<std::string,dns_entry>::const_iterator const it = entries.find(domain); return (it == entries.end()) ? dns_entry() : it->second; } void update_or_add_entry(std::string const & domain, dns_entry const& dns_details) { std::lock_guard<boost::shared_mutex> lk(entry_mutex); entries[domain] = dns_details; } }; std::condition_variable 和 std::condition_variable_any <condition_variable> 两者都需要与一个互斥量一起才能工作 std::condition_variable和std::mutex一起工作 std::condition_variable_any 可以和任何满足最低标准的互斥量一起工作