前言
什么是线程?
在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
一切进程至少都有一个执行线程
线程在进程内部运行,本质是在进程地址空间内运行
透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
一、线程的优缺点
1.线程的优点:
创建一个新线程的代价要比创建一个新进程小得多
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
线程占用的资源要比进程少很多
能充分利用多处理器的可并行数量
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
2.线程的缺点
性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造响。
编程难度提高:编写与调试一个多线程程序比单线程程序困难得多
3.线程异常
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
4.线程用途
合理的使用多线程,能提高CPU密集型程序的执行效率。
合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)。
二、C++线程库
1.thread类的简单介绍
在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差 。 C++11 中最重要的特性就是对线程进行支持了,使得 C++ 在
并行编程时不需要依赖第三方库 ,而且在原子操作中还引入了原子类的概念。要使用标准库中的
线程,必须包含 < thread > 头文件。
以下是线程相关的函数:
函数名: thread() 功能:构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
函数名:
thread(fn,
args1, args2,
...) 这里的...是可变参数列表,我会在后面深入学习线程的文章中详细介绍。 功能: 构造一个线程对象,并关联线程函数fn,args1,args2,...为线程函数的参数
函数名:get_id() 功能:获取线程id
函数名: jionable() 功能:线程是否还在执行,joinable代表的是一个正在执行中的线程。
函数名: jion() 功能:该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
函数名: detach() 功能:在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关
注意:
1. 线程是操作系统中的一个概念, 线程对象可以关联一个线程,用来控制线程以及获取线程的
状态。
2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。如下图:
#include <thread> #include <iostream> using namespace std; int main() { std::thread t1; cout << t1.get_id() << endl; return 0; }
上图中get_id的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中包含一个结构体,如下:
// vs下查看 typedef struct { /* thread identifier for Win32 */ void *_Hnd; /* Win32 HANDLE */ unsigned int _Id; } _Thrd_imp_t;
3.当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。 线程函数一般情况下可按照以下三种方式提供: 1.函数指针 2.lambda表达式 3.函数对象
以下为三种方式的代码:
#include <iostream> using namespace std; #include <thread> void ThreadFunc(int a) { cout << "Thread1" << a << endl; } class TF { public: void operator()() { cout << "Thread3" << endl; } }; int main() { // 线程函数为函数指针 thread t1(ThreadFunc, 10); // 线程函数为lambda表达式 thread t2([]{cout << "Thread2" << endl; }); // 线程函数为函数对象 TF tf; thread t3(tf); t1.join(); t2.join(); t3.join(); cout << "Main thread!" << endl; return 0; }
4.thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
5.可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效:
1.采用无参构造函数构造的线程对象。
2.线程对象的状态已经转移给其他线程对象
3.线程已经调用jion或者detach结束
2.线程函数参数
线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。
演示代码如下:
#include <thread> void ThreadFunc1(int& x) { x += 10; } void ThreadFunc2(int* x) { *x += 10; } int main() { int a = 10; // 在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际 引用的是线程栈中的拷贝 thread t1(ThreadFunc1, a); t1.join(); cout << a << endl; // 如果想要通过形参改变外部实参时,必须借助std::ref()函数 thread t2(ThreadFunc1, std::ref(a); t2.join(); cout << a << endl; // 地址的拷贝 thread t3(ThreadFunc2, &a); t3.join(); cout << a << endl; return 0; }
注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数。
以上就是线程的一小部分的简单学习,我们这篇文章的深度很浅很浅,就是给初学者看一看,对于更深入的c++11的所有新特性我会在后面的文章中详细并且深入的讲解。
总结
c++11新特性中的lambda和线程都是值得学习的,由于我现在对lambda和线程学习的很浅所以本篇文章只是讲了我目前所简单学到的一些知识,在后面的文章中我会详细的重新介绍lambda函数以及线程等c++11新特性的学习。