1.创建线程Thread
首先要引入头文件#include,管理线程的函数和类在该头文件中声明,其中包括std::thread类。
语句"std::thread th1(proc1);"创建了一个名为th1的线程,并且线程th1开始执行。
实例化std::thread类对象时,至少需要传递函数名作为参数。如果函数为有参函数,如"void proc2(int a,int b)“,那么实例化std::thread类对象时,则需要传递更多参数,参数顺序依次为函数名、该函数的第一个参数、该函数的第二个参数,···,如"std::thread th2(proc2,a,b);”。
总体来说,std::thread的构造函数会拷贝传入的参数:
a.当传入参数为基本数据类型(int,char,string等)时,会拷贝一份给创建的线程;
b. 当传入参数为指针时,会浅拷贝一份给创建的线程,也就是说,只会拷贝对象的指针,不会拷贝指针指向的对象本身。
c. 当传入的参数为引用时,实参必须用ref()函数处理后传递给形参,否则编译不通过,此时不存在“拷贝”行为。
使用std::thread时,对创建的线程有两种操作:等待/分离,也就是join/detach操作。
join() 的意思是:父线程等待子线程结束,并回收资源;
detach() 的含义是:父线程和子线程相互分离,即使父线程结束了,只要主线程没有结束,子线程就会继续正常运行。如果父线程就是主线程,主线程结束,子线程也会结束。可能存在的问题:若子线程还没来得及清理垃圾的话,主线程就结束了,那么就会导致内存溢出。
join是等待被创建线程的结束之后回收它的资源。因此,join的调用位置就比较关键。两种解决方式,防止join阻塞或者资源泄露:
a.主动让被创建线程结束,如设置退出标志位等;
b.在对象的析构函数中调用线程的join函数,等待资源回收。
使用joinable()来判断join()可否调用。同样,detach()也只能调用一次,一旦detach()后就无法join()了,有趣的是,detach()可否调用也是使用joinable()来判断。
注意:使用detach()时,可能存在主线程比子线程先结束的情况,主线程结束后会释放掉自身的内存空间;在创建线程时,如果std::thread类传入的参数含有引用或指针,则子线程中的数据依赖于主线程中的内存,主线程结束后会释放掉自身的内存空间,则子线程会出现错误。
ps:因为c++11的多线程编程以boost库的多线程编程为基础,所以两者在用法上很相似。
2.互斥量(锁)使用
2.1 什么是互斥量(锁)?
这样比喻:单位上有一台打印机(共享数据a),你要用打印机(线程1要操作数据a),同事老王也要用打印机(线程2也要操作数据a),但是打印机同一时间只能给一个人用,此时,规定不管是谁,在用打印机之前都要向领导申请许可证(lock),用完后再向领导归还许可证(unlock),许可证总共只有一个,没有许可证的人就等着在用打印机的同事用完后才能申请许可证(阻塞,线程1lock互斥量后其他线程就无法lock,只能等线程1unlock后,其他线程才能lock)。那么,打印机就是共享数据,访问打印机的这段代码就是临界区,这个必须互斥使用的许可证就是互斥量(锁)。
互斥量是为了解决数据共享过程中可能存在的访问冲突的问题。这里的互斥量保证了使用打印机这一过程不被打断。
互斥量:采用互斥对象机制。只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。
2.2 互斥量(锁)怎么使用?
首先需要#include;(std::mutex和std::lock_guard都在头文件中声明。)
然后需要实例化std::mutex对象;
需要在进入临界区之前对互斥量加锁,退出临界区时对互斥量解锁;
lock()与unlock():
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;//实例化m对象,不要理解为定义变量
void proc1(int a)
{
m.lock();
cout << "proc1函数正在改写a" << endl;
cout << "原始a为" << a << endl;
cout << "现在a为" << a + 2 << endl;
m.unlock(