Part1.【 thread 】(#include <thread>)
C++中的多线程,常通过thread类来定义一个thread对象(子线程)来实现。
thread t1 (func, arg1, arg2...);
其中func可以是一个函数名,或者函数对象;后边跟这个对象的参数;
在定义一个子线程以后,要确定他是join()或者detach()。
* t1.join():表示当前线程将在此处等待t1执行完相应操作后继续执行下面的程序(已经在运行状态的程序部分不会停止)。
* t1.detach(): 表示当前程序将不会等待以及管理t1子程序的运行。
* 一个子线程只能被join()或者detach()一次;一个子线程被detach后不可以再被join
*必须规定子线程是join或者detach,否则程序会终止(terminate)
来看一个简单的代码示例:
#include <iostream> #include <thread> using namespace std; void func() { for(int i = 0; i < 10; ++i) { cout << "From sub thread" << i << endl; } } int main() { thread t1(func); for(int j = 0; j < 10; ++j) { cout << "From main thread" << j << endl; } t1.join(); }
***************************************************************************************************************************************
Part2.【 mutex/locker/condition 】(#include <mutex>; #include <conditional_variable>)
当多个线程同时使用或操作相同资源是,常会造成资源的混乱,例如两个线程同时在同一个队列或文件中进行写操作。
这种情况下我们可以用mutex类来生成一个对象,用来确保同一资源在同一时间内只被一个线程访问。
具体原理是,在使用某一资源时,如果前边有某个mutex对象mu.lock()的操作,则需先判断mu是否处于unlock的状态,如果没有的话说明被mu保护的该资源在被其他线程使用,则需要等待。
此外,由于一些特殊状况,例如异常,mu没有执行unlock, 会使被mutex对象保护的资源一直处于不能访问状态,我们经常把mutex与一些locker类结合使用,当locker对象生命周期结束时,无论通过任何方式结束,mutex对象都会被自动析构,资源便可释放。
常用的locker有:locker_guard和unique_locker。前一种效率较高,后一种功能较多更灵活,可控制locker的状态,也可与conditional_varialble结合使用。
* 死锁现象:
使用mutex和locker时要注意避免死锁现象,例如:线程A拥有mu1保护的资源,在等待mu2保护的资源;而线程B拥有mu2保护的资源,在等待mu1保护的资源
避免的方法有:
1. 一个mutex对象尽量只对应一个资源;
2. 若不得不安排多个mu对应一个资源,保持这些mu对象的顺序一致;最好使用std::lock(),包含一些避免死锁的机制
3. 不要在有对象被mutex保护时去call一个用户function/未知的function;
* conditional_variable: (线程间交互)
在一些情况下,两个线程需要交互,其中一个线程需要等待另一个线程执行完一些操作才应该执行。
例如线程A负责往队列里放东西,线程B负责从队尾拿东西;如果A中push的操作还没有完成,B会一直查看队列中是否有元素,会导致性能的损耗。
这时会使用conditional_variable来建立一个A与B交流的方式。
比如下面一个列子:
#include <iostream> #include <thread> #include <mutex> #include <deque> #include <condition_variable> using namespace std; deque<int> dq; mutex mu; condition_variable cond; void func1() { int data = 1; while(data <= 3) { unique_lock<mutex> locker(mu); dq.push_front(data++); locker.unlock(); cond.notify_one(); //通知线程t2 this_thread::sleep_for(chrono::seconds(1)); } } void func2() { int data = 0; while(data != 3) { unique_lock<mutex> locker(mu); cond.wait(locker, [](){ return !dq.empty();}); //当获得资源访问权时cond会先让出使用权,即将locker中mutex对象处于unlock状态 //所以被其保护的资源dq可以先被其他线程使用,直到接收到cond的notify, //才会再次把locker处于lock的状态 data = dq.back(); dq.pop_back(); cout << data << endl; locker.unlock(); } } int main() { thread t1(func1); thread t2(func2); t1.join(); t2.join(); }
t1线程向dq中放入元素,t2从dq中取出元素;
输出结果为:
1
2
3
***********************************************************************************************************************************
Part3. 【 future/promise/async 】(#include <future> )
有时不同线程或程序的不同地方之间通信会有比较复杂的情况,
Case1. 程序块A需要程序块B执行的函数的返回值。但程序块A不能控制程序块B什么时候结束,则可通过future对象(可以看做一个channel)来获取到将来某时刻B响应函数的返回值。
Eg. future<int> fu = async(myfunc, args...);
int x = fu.get() //可获得函数myfunc的返回值
* async函数可理解为一个会并发进行的操作,与thread不同的是可以指定async中的操作是与当前线程同时进行还是需要时再触发,
* async函数返回一个future对象
* 同一个future对象只可以使用一次get()
Case2. 程序块A需要程序块B指定一些参数,但是在定义A时B还未设置该参数,则可传入一个future对象,并将其值等于一个promise对象,然后通过promise对象之后再设置具体的值。
Eg. future<int> fu;
promise<int> pr;
async(myfunc, ref(fu));...
pr.set_value(...);
要注意的是promise对象使用后,一定要设置相应的值,不然会报错。
来看一个完整示例:
#include <iostream> #include <thread> #include <future> #include <utility> using namespace std; int myPow(future<pair<int,int>>& fp) { auto p = fp.get(); int n = p.first, x = p.second; int res = 1; for(int i = 1; i <= x; ++i) res *= n; return res; } int main() { promise<pair<int, int>> pr; future<pair<int, int>> fp = pr.get_future(); future<int> fu = async(myPow, std::ref(fp)); pr.set_value(make_pair(2,4)); int res = fu.get(); cout << res << endl; 」
myPow来计算一个数字x的n次方;通过future对象fp, 把参数x和n传入。而fp是通过promise对象pr来设置的。
此外myPow计算的结果又通过future对象fu传回。
运行结果为:16
************************************************************************************************************
Part4【 packaged_task 】
另外一个callable 类型,基本定义为:
packaged_task<T1(T2...)> pt (myFunc);
其中T1是函数myFunc返回类型,T2..是myFunc的各函数类型;运行函数时应该使用:pt(arg1, arg2...);
一个packaged_task类型对象可以通过调用get_future()返回一个future对象
* 返回future类型的途径:
1. async(),函数直接返回类型;
2. promise对象,调用get_future();
3. packaged_task对象,调用get_future();
一个代码示例:
#include <iostream> #include <thread> #include <future> #include <queue> using namespace std; int factorial(int n) { int res = 1; for(int i = n; i >= 1; --i) res *= n; return n; } queue<packaged_task<int()> q; mutex mu; conditional_variable cond; void thread_1() { unique_lock<mutex> locker(mu); cond.wait(locker, [](){return !q.empty();}); packaged_task<int()> t = move(q.front()); q.pop(); t(); } int main(){ thread t1(thread_1); packaged_task<int()> t(bind(factorial,6)); unique_lock<mutex> locker(mu); q.push(t); cond.notify_one(); t1.join(); int x = t.get_future().get(); cout << x << endl; }
主线程中:
定义要执行thread_1的子线程t1,
定义要执行factorial的packaged_task t,并把他push到任务队列中,
通知t1:
t1中:
t1从任务队列中拿出t并执行factorial,执行结果被保存在t中(future channel中)
主线程中:
t1结束后,从t中拿到结果
运行结果为:16
**以上内容参考相关视频: https://www.bilibili.com/video/BV1ut411y7u5**