1.thread – 线程 包含在std空间< thread>头文件中
1.1 常用得成员函数
- get_id() – 获取线程id
- joinable() – 判断线程是否可以等待加入
- join() – 等待线程执行完成之后才返回
- detach() – 将本线程从调用线程中分离出去,允许本线程独立运行,但是当主线程结束得时候,本线程即使没有结束也会被强制杀死,调用了detach之后得线程调用join是没有用得
- yield – 线程放弃当前执行,操作系统调用别得线程执行
1.2 一个简单得创建线程得过程 – 编译得时候加上选项 -std=c++11
ps:pthread_create 创建线程得函数是C++ 98 的创建接口 只支持linux 而thread是c11的新特性
#include<iostream>
#include<thread>
using namespace std;
void func1()
{
cout<<"func1 foo" <<endl;
}
void func2(int a, int b)
{
cout <<"func2 foo" <<"a ==" <<a <<" b==" <<b <<endl;
}
class A
{
public:
static void func3(int a)
{
cout << "static func3 foo" <<endl;
}
void func4(int a)
{
cout << "static fun4 foo "<<endl;
}
};
int main()
{
std::thread t1(func1);
t1.join();
int a=10,b=20;
std::thread t2(func2,a,b);
t2.join();
std::thread t3(&A::func3,a);
t3.join();
//A Aa; -- 切记此种用法是错误得,不能绑定到非静态成员函数
//std::thread t4(Aa.func4,a);
//t4.join();
}
2. 互斥量 - - mutex 包含在std空间,< mutex >头文件中 用来处理多线程之间对共享区域的原子操作
2.1 – std::lock_guard() RAII(资源分配时初始化)
std::lock_guard类是为了mutex RAII调用的一套类模板。类模板除了构造函数和析构函数外没有其它成员函数,该模板的工作原理是在构造mutex对象的时候 调用构造函数的时候(对象创建的时候)进行加锁,在析构函数的时候(对象生命周期结束的时候)进行解锁处理,这样可以简化对互斥量的加锁和解锁的处理
2.2 独占的互斥量 不能递归操作 std::mutex
- 构造函数 std::mutex不允许拷贝构造 ,不允许move拷贝,刚定义的mutex变量处于unlocked状态
- lock() 调用线程将锁住该互斥量 会有三种情况 1. 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住 直到调用unlock() 2.如果当前互斥量被其他线程锁住那么线程调用lock函数将会被 阻塞 3. 如果当前互斥量被当前线程锁住,则会产生死锁,相当于一个线程里面调用了2次lock
- try_lock 调用线程将锁住该互斥量 会有三种情况 1. 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住 直到调用unlock() 2.如果当前互斥量被其他线程锁住那么线程调用lock函数,当前线程返回false, 不会阻塞 3. 如果当前互斥量被当前线程锁住,则会产生死锁,相当于一个线程里面调用了2次try_lock()
- unlock() 解锁
2.3 带超时的独占互斥量,不能递归使用 std::time_mutex
*std::time_mutex比std::mutex 多了两个超时获取锁的接口 try_lock_for 和try_lock_until
try_lock_for 尝试在某个时间段内进行加锁
- 如果此时互斥量未被上锁,则获得改锁
- 如果此时互斥量已经被其他线程获取,则当前线程会阻塞,直到获得互斥量的锁,但是阻塞的时间不会超过传入的参数real_time,超时返回false
- 如果此时互斥量已经在当前线程获取了一次锁,则会产生死锁
try_lock_until与try_lock_for类似只是不是时间段而是时间点
2.4 可递归使用的互斥量,不带超时功能 std::recursive_mutex
std::recursiev_mutex 允许同一线程获取多次该互斥锁,而不会产生死锁的问题
#include<iostream>
#include<thread>
#inlude<mutex>
class Complex
{
public:
std::recursive_mutex mu;
int i;
Complex():i(0){
}
void mul(int x)
{
std::lock_guard<std::recursive_mutex> lock(mu);
i*=x;
}
void div(int x)
{
std::lock_guard<std::recursive_mutex>lock(mu);
i/=x;
}
void both(x, y)
{
std::lock_guard<std::recursive_mutex>lock(mu);
mul(x);
div(y);
}
};
int main()
{
Complex complex;
complex.both(2,3);
cout << "finish" <<endl;
return 0;
}
2.5 可递归使用的互斥量,带超时功能 std::recursive_timed_mutex
结合了std::recursive_mutex和time_mutex两者的功能
2.6 lock_guard 和unique_lock的使用和区别
*unique_lock和lock_guard 都能实现自动的加锁和解锁处理,但是unique_lock更加的灵活,能实现更多的功能,因为unique_lock可以进行临时的加锁和解锁,不一定非等到析构函数
*
3. 条件变量 – 头文件< condition_variable >
使用条件变量一定要使用unique_lock,因为条件变量在wait的时候会先unlock然后进入休眠,但是lock_guard并无此接口
3.1 使用过程
- 拥有条件变量的线程获得互斥量
- 循环检测某个条件,如果条件满足则向下执行,如果不满足就则阻塞置条件满足的时候
- 某个线程满足条件执行完成之后使用notify_one 或notify_all唤醒一个或所有等待的线程
3.2 成员函数
1. wait
void wait (unique_lock<mutex>& lck);//只包含unique_lock对象
template <class Predicate>
//包含unique_lock对象和 Predicate条件变量
void wait (unique_lock<mutex>& lck, Predicate pred)
- 工作原理
1.当前线程调用wait之后会进入休眠状态,并且在休眠之前会进行unlock互斥量(为什么使用unique_lock的原因),直到另外的线程调用notify_one 或者notify_all唤醒该线程,一旦线程获得notify则wait 函数会自动添加lock 同理不能使用lock_guard
2.如果wait没有第二个参数,则在第一次调用的时候默认条件不满足,释放互斥量并且阻塞到本行,直到某个线程nofity_one或notify_all为止 如果该线程在notify中被唤醒 无条件的执行下面的操作
3.如果wait有第二个参数 ,如果第二个参数的条件不满足,则线程会释放互斥量并进入阻塞状态,直到notify_ont和notify_all唤醒为止,被唤醒后,线程将尝试获取互斥量,如果得不到线程将会阻塞在这里,如果得到了,就判断第二个参数得条件是否满足 如果满足则继续执行下面得内容
ps:wait_for和wait_until 两者都可以加上一个时间(一个是时间段,一个是时间点,两者在线程获得唤醒之前和超时之前都会进入阻塞状态,如果被notify_one或则notify_all唤醒,或者到了超时时间了,也会进行唤醒),在wait的时候会释放锁并进