C++ 11新标准中,正式的为该语⾔引⼊了多线程概念。新标准提供了⼀个线程库thread,通过创建⼀个thread对象来管理C++程序中的多线程。
本⽂简单聊⼀下C++多线程相关的⼀些概念及thread的基本⽤法。
0. 并⾏执⾏
程序并⾏执⾏两个必要条件:
多处理器(multiple processors)or 多核处理器(multicore processors)软件并⾏
软件并发执⾏可分为两⼤类:
1. 多线程并发 (同⼀个进程的多个线程并⾏);2. 多进程并发 (不同进程并⾏);
对于多线程,主要关注的是线程间的同步措施,⽤于确保线程安全;对于多进程,主要关注的是进程间的通信机制,⽤于进程间传递消息和数据;
由于C++ 标准中没有多进程之间通信相关的标准,这些只能依赖于特定平台的API。本⽂只关注多线程相关。
1. C++多线程平台
C++11之前,window和linux平台分别有各⾃的多线程标准。使⽤C++编写的多线程往往是依赖于特定平台的。
Window平台提供⽤于多线程创建和管理的win32 api;
Linux下则有POSIX多线程标准,Threads或Pthreads库提供的API可以在类Unix上运⾏;
在C++11新标准中,可以简单通过使⽤hread库,来管理多线程。thread库可以看做对不同平台多线程API的⼀层包装;因此使⽤新标准提供的线程库编写的程序是跨平台的。
2. pthread 或 C++ 11 thread
pthreads 是linux下的C++线程库,提供了⼀些线程相关的操作,⽐较偏向于底层,对线程的操作也是⽐较直接和⽅便的;
#include pthread_create (thread, attr, start_routine, arg) linux上对于pthread的使⽤需要连接pthread库(有些编辑器可能需要 -std=c++11): g++ source.cpp -lpthread -o source.o 尽管⽹上对C++ 11新标准中的thread类有很多吐槽,但是作为C++第⼀个标准线程库,还是有⼀些值得肯定的地⽅的,⽐如跨平台,使⽤简单。 ⽽且新标准中可以⽅便的使⽤RAII来实现lock的管理等。 如果你想深⼊研究⼀下多线程,那么pthread是⼀个不错的选择。如果想要跨平台或者实现⼀些简单的多线程场景⽽不过多关注细节,那么权威的标准库thread是不⼆之选。 总之没有好与坏之分,适合就好。可以的话可以都了解⼀下。本⽂主要介绍后者。 3. 先理论后实践 对于多线程相关的学习,先弄清楚线程相关的⼀些概念,是很重要的。⽐如线程安全、线程同步与互斥关系、线程如何通信、与进程的关系如何等。不然实际写多线程程序是会碰到太多的问题,例如: 程序死锁,⽆响应;执⾏结果不符合预期; 多线程性能并没有很⼤提升;理不清程序执⾏流程;不知道怎么调试;程序运⾏时好时坏; 光线程安全就有很多理论要了解,这些光靠调试程序,根据结果来猜测是不可⾏的。 关于多线程相关的概念可以参考我之前以Python为例介绍线程的博⽂: ;; 4. thread 多线程实例 看⼀下C++11 使⽤标准库thread创建多线程的例⼦: 1 #include 5 using namespace std; 6 7 int tstart(const string& tname) { 8 cout << \"Thread test! \" << tname << endl; 9 return 0;10 }11 12 int main() { 13 thread t(tstart, \"C++ 11 thread!\");14 t.join(); 15 cout << \"Main Function!\" << endl;16 } 多线程标准库使⽤⼀个thread的对象来管理产⽣的线程。该例⼦中线程对象t表⽰新建的线程。4.1 标准库创建线程的⽅式 打开thread头⽂件,可以清楚的看到thread提供的构造函数。 1. 默认构造函数 thread() noexcept; 2. 接受函数及其传递参数的构造函数 template 5. 拷贝赋值运算符 thread& operator=(const thread&) = delete; 其中拷贝构造函数和拷贝赋值运算符被禁⽤,意味着std::thread对象不能够被拷贝和赋值到别的thread对象;默认构造函数构造⼀个空的thread对象,但是不表⽰任何线程; 接受参数的构造函数创建⼀个表⽰线程的对象,线程从传⼊的函数开始执⾏,该对象是joinable的; move构造函数可以看做将⼀个thread对象对线程的控制权限转移到另⼀个thread对象;执⾏之后,传⼊的thread对象不表⽰任何线程; int main(){ int arg = 0; std::thread t1; // t1 is not represent a thread std::thread t2(func1, arg + 1); // pass to thread by value std::thread t3(func2, std::ref(arg)); // pass to thread by reference std::thread t4(std::move(t3)); // t4 is now running func2(). t3 is no longer a thread //t1.join() Error! t2.join(); //t3.join() Error! t4.join();} 多数情况下我们使⽤的是上⾯第⼆种创建线程的⽅式。下⾯看⼀下join和detach。4.2 join && detach 对于创建的线程,⼀般会在其销毁前调⽤join和detach函数; 弄清楚这两个函数的调⽤时机和意义,以及调⽤前后线程状态的变化⾮常重要。 join 会使当前线程阻塞,直到⽬标线程执⾏完毕; 只有处于活动状态线程才能调⽤join,可以通过joinable()函数检查;joinable() == true表⽰当前线程是活动线程,才可以调⽤join函数;默认构造函数创建的对象是joinable() == false; join只能被调⽤⼀次,之后joinable就会变为false,表⽰线程执⾏完毕;调⽤ ternimate()的线程必须是 joinable() == false; 如果线程不调⽤join()函数,即使执⾏完毕也是⼀个活动线程,即joinable() == true,依然可以调⽤join()函数;detach 将thread对象及其表⽰的线程分离; 调⽤detach表⽰thread对象和其表⽰的线程完全分离; 分离之后的线程是不在受约束和管制,会单独执⾏,直到执⾏完毕释放资源,可以看做是⼀个daemon线程;分离之后thread对象不再表⽰任何线程; 分离之后joinable() == false,即使还在执⾏; join实例分析: int main() { thread t(tstart, \"C++ 11 thread!\"); cout << t.joinable() << endl; if (t.joinable()) t.join(); //t.detach(); Error cout << t.joinable() << endl; // t.join(); Error cout << \"Main Function!\" << endl; system(\"pause\");} 简单来说就是只有处于活动状态的线程才可以调⽤join,调⽤返回表⽰线程执⾏完毕,joinable() == false. inline void thread::join() { // join thread if (!joinable()) _Throw_Cpp_error(_INVALID_ARGUMENT); const bool _Is_null = _Thr_is_null(_Thr); // Avoid Clang -Wparentheses-equality ... ...} 将上⾯的t.join()换成是t.detach()会得到相同的结果. void detach() { // detach thread if (!joinable()) _Throw_Cpp_error(_INVALID_ARGUMENT); _Thrd_detachX(_Thr); _Thr_set_null(_Thr); } 上⾯是thread⽂件中对detach的定义,可以看出只有joinable() == true的线程,也就是活动状态的线程才可以调⽤detach。 ~thread() _NOEXCEPT { // clean up if (joinable()) _XSTD terminate(); } 当线程既没有调⽤join也没有调⽤detach的时候,线程执⾏完毕joinable() == true,那么当thread对象被销毁的时候,会调⽤terminate()。4.3 获取线程ID 线程ID是⼀个线程的标识符,C++标准中提供两种⽅式获取线程ID;1. thread_obj.get_id();2. std::this_thread::get_id() 有⼀点需要注意,就是空thread对象,也就是不表⽰任何线程的thread obj调⽤get_id返回值为0;此外当⼀个线程被detach或者joinable() == false时,调⽤get_id的返回结果也为0。 cout << t.get_id() << ' ' << this_thread::get_id() << endl;//t.detach();t.join(); cout << t.get_id() << ' ' << std::this_thread::get_id() << endl; 4.4 交换thread表⽰的线程 除了上⾯介绍的detach可以分离thread对象及其所表⽰的线程,或者move到别的线程之外,还可以使⽤swap来交换两个thread对象表⽰的线程。 实例来看⼀下两个线程的交换。 int tstart(const string& tname) { cout << \"Thread test! \" << tname << endl; return 0;} int main() { thread t1(tstart, \"C++ 11 thread_1!\"); thread t2(tstart, \"C++ 11 thread_2!\"); cout << \"current thread id: \" << this_thread::get_id() << endl; cout << \"before swap: \"<< \" thread_1 id: \" << t1.get_id() << \" thread_2 id: \" << t2.get_id() << endl; t1.swap(t2); cout << \"after swap: \" << \" thread_1 id: \" << t1.get_id() << \" thread_2 id: \" << t2.get_id() << endl; //t.detach(); t1.join(); t2.join();} 结果: Thread test! C++ 11 thread_1!Thread test! C++ 11 thread_2!current thread id: 39308 before swap: thread_1 id: 26240 thread_2 id: 37276after swap: thread_1 id: 37276 thread_2 id: 26240 下⾯是thread::swap函数的实现。 void swap(thread& _Other) _NOEXCEPT { // swap with _Other _STD swap(_Thr, _Other._Thr); } 可以看到交换的过程仅仅是互换了thread对象所持有的底层句柄; 关于C++ 多线程新标准thread的基本介绍就到这⾥了,看到这⾥应该有⼀个简单的认识了。关于线程安全和管理等⾼级话题,后⾯有空在写⽂章介绍。 因篇幅问题不能全部显示,请点此查看更多更全内容