您的当前位置:首页C++std::thread概念介绍

C++std::thread概念介绍

2022-04-23 来源:爱问旅游网
C++std::thread概念介绍

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 2 #include 3 #include 4

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 explicit thread(_Fn&& _Fx, _Args&&... _Ax)3. move构造函数 thread(thread&& _Other) noexcept;4. 拷贝构造函数 thread(const thread&) = delete;

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的基本介绍就到这⾥了,看到这⾥应该有⼀个简单的认识了。关于线程安全和管理等⾼级话题,后⾯有空在写⽂章介绍。

因篇幅问题不能全部显示,请点此查看更多更全内容