C++ 11新标准中,正式的为该语言引入了多线程概念。新标准提供了一个线程库thread,通过创建一个thread对象来管理C++程序中的多线程。
本文简单聊一下C++多线程相关的一些概念及thread的基本用法。
0. 并行执行
程序并行执行两个必要条件:
- 多处理器(multiple processors)or 多核处理器(multicore processors)
- 软件并行
软件并发执行可分为两大类:
- 多线程并发 (同一个进程的多个线程并行);
- 多进程并发 (不同进程并行);
对于多线程,主要关注的是线程间的同步措施,用于确保线程安全;
对于多进程,主要关注的是进程间的通信机制,用于进程间传递消息和数据;
由于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.h>
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<iostream> 2 #include<thread> 3 #include<string> 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提供的构造函数。
- 默认构造函数 thread() noexcept;
- 接受函数及其传递参数的构造函数 template <class _Fn, class... _Args, ...> explicit thread(_Fn&& _Fx, _Args&&... _Ax)
- move构造函数 thread(thread&& _Other) noexcept;
- 拷贝构造函数 thread(const thread&) = delete;
- 拷贝赋值运算符 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::r