
本文共 5263 字,大约阅读时间需要 17 分钟。
多线程是实现并发(双核的真正并行或者单核机器的任务切换都叫并发)的一种手段,多线程并发即多个线程同时执行,一般而言,多线程并发就是把一个任务拆分为多个子任务,然后交由不同线程处理不同子任务,使得这多个子任务同时执行。
标准库提供了std::thread类来创建和管理线程,std::future类模板来获取异步操作的结果。1 创建线程
首先要引入头文件#include<thread>
,C++11中管理线程的函数和类在该头文件中声明,其中包括std::thread类。
使用C++线程库启动线程,可以归结为构造std::thread对象。
- 语句
thread th1(proc1);
创建了一个名为th1的线程,并且线程th1开始执行函数proc1。 - 语句
th1 = thread(proc1)
功能同上。 下面是创建线程数组的示例:
实例化thread
类对象时,至少需要传递函数名作为参数。如果函数为有参函数,如void proc2(int a,int b)
,那么实例化thread
类对象时,则需要传递更多参数,参数顺序依次为函数名、该函数的第一个参数、该函数的第二个参数,···,如thread th2(proc2,a,b);
。这里的传参,后续章节还会有详解与提升。
只要创建了线程对象(前提是,实例化std::thread对象时传递了“函数名/可调用对象”作为参数),线程就开始执行。
2 线程阻塞
线程启动后,一定要在和线程相关联的std::thread对象销毁前,对线程运用join()或者detach()方法。
join()与detach()都是std::thread类的成员函数,是两种线程阻塞方法,两者的区别是是否等待子线程执行结束。
2.1 join
所谓join,是等待调用线程运行结束后当前线程再继续运行,例如,主函数中有一条语句th1.join(),那么执行到这里,主函数阻塞,直到线程th1运行结束,主函数再继续运行。

2.2 detach
称为分离线程函数,使用detach()函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束
通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束,可能会在后台监视文件系统,还有可能对缓存进行清理,亦或对数据结构进行优化。3 互斥量
3.1 基本用法:lock+unlock
互斥量是为了解决数据共享过程中可能存在的访问冲突的问题。首先需要#include<mutex>;
,然后需要实例化mutex
对象;最后需要在进入临界区之前对互斥量加锁,退出临界区时对互斥量解锁;至此,互斥量走完了它的一生。
#include#include #include 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();}void proc2(int a){ m.lock(); cout << "proc2函数正在改写a" << endl; cout << "原始a为" << a << endl; cout << "现在a为" << a + 1 << endl; m.unlock();}int main(){ int a = 0; thread p1(proc1, a); thread p2(proc2, a); p1.join(); p2.join(); return 0;}
不推荐实直接去调用成员函数lock(),因为如果忘记unlock(),将导致锁无法释放,使用lock_guard或者unique_lock则能避免忘记解锁带来的问题。
3.2 lock_guard类
std::lock_guard()会在其构造函数中进行加锁,在其析构函数中进行解锁。最终的结果就是:创建即加锁,作用域结束自动解锁,从而使用lock_guard()
就可以替代lock()与unlock()。
lock_guard
在合适的地方被析构(在互斥量锁定到互斥量解锁之间的代码叫做临界区(需要互斥访问共享资源的那段代码称为临界区),临界区范围应该尽可能的小,即lock互斥量后应该尽早unlock),通过使用{}来调整作用域范围,可使得互斥量m在合适的地方被解锁。 下面是例子: #include#include #include using namespace std;mutex m;//实例化m对象,不要理解为定义变量void proc1(int a){ lock_guard g1(m);//用此语句替换了m.lock();lock_guard传入一个参数时,该参数为互斥量,此时调用了lock_guard的构造函数,申请锁定m cout << "proc1函数正在改写a" << endl; cout << "原始a为" << a << endl; cout << "现在a为" << a + 2 << endl;}//此时不需要写m.unlock(),g1出了作用域被释放,自动调用析构函数,于是m被解锁void proc2(int a){ { lock_guard g2(m); cout << "proc2函数正在改写a" << endl; cout << "原始a为" << a << endl; cout << "现在a为" << a + 1 << endl; }//通过使用{}来调整作用域范围,可使得m在合适的地方被解锁 cout << "作用域外的内容3" << endl; cout << "作用域外的内容4" << endl; cout << "作用域外的内容5" << endl;}int main(){ int a = 0; thread proc1(proc1, a); thread proc2(proc2, a); proc1.join(); proc2.join(); return 0;}
lock_gurad
也可以传入两个参数,第一个参数为adopt_lock
标识时,表示构造函数中不再进行互斥量锁定,因此此时需要提前手动锁定。
3.3 unique_lock类
std::unique_lock类似于lock_guard,只是std::unique_lock用法更加丰富,同时支持std::lock_guard()的原有功能。 使用std::lock_guard后不能手动lock()与手动unlock();使用std::unique_lock后可以手动lock()与手动unlock(); std::unique_lock的第二个参数,除了可以是adopt_lock,还可以是try_to_lock与defer_lock;
try_to_lock: 尝试去锁定,得保证锁处于unlock的状态,然后尝试现在能不能获得锁;尝试用mutx的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里,并继续往下执行;
defer_lock: 始化了一个没有加锁的mutex;
#include#include #include using namespace std;mutex m;void proc1(int a){ unique_lock g1(m, defer_lock);//始化了一个没有加锁的mutex cout << "xxxxxxxx" << endl; g1.lock();//手动加锁,注意,不是m.lock()! 注意,不是m.lock()! m已经被g1接管了; cout << "proc1函数正在改写a" << endl; cout << "原始a为" << a << endl; cout << "现在a为" << a + 2 << endl; g1.unlock();//临时解锁 cout << "xxxxx" << endl; g1.lock(); cout << "xxxxxx" << endl;}//自动解锁void proc2(int a){ unique_lock g2(m,try_to_lock);//尝试加锁一次,但如果没有锁定成功,会立即返回,不会阻塞在那里,且不会再次尝试锁操作。 if(g2.owns_lock){//锁成功 cout << "proc2函数正在改写a" << endl; cout << "原始a为" << a << endl; cout << "现在a为" << a + 1 << endl; }else{//锁失败则执行这段语句 cout <<""<
mutex对象的所有权不需要手动转移给unique_lock , unique_lock对象实例化后会直接接管mutex。
mutex m;{ unique_lockg2(m,defer_lock); unique_lock g3(move(g2));//所有权转移,此时由g3来管理互斥量m g3.lock(); g3.unlock(); g3.lock();}
4 异步线程future
主线程从调用get方法开始进行堵塞。需要#include<future>
#include#include #include using namespace std;double t1(const double a, const double b){ double c = a + b; sleep(3);//假设t1函数是个复杂的计算过程,需要消耗3秒 return c;}int main() { double a = 2.3; double b = 6.7; future fu = async(t1, a, b);//创建异步线程线程,并将线程的执行结果用fu占位; cout << "正在进行计算" << endl; cout << "计算结果马上就准备好,请您耐心等待" << endl; cout << "计算结果:" << fu.get() << endl;//阻塞主线程,直至异步线程return //cout << "计算结果:" << fu.get() << endl;//取消该语句注释后运行会报错,因为future对象的get()方法只能调用一次。 return 0;}
发表评论
最新留言
关于作者
