C++11的标准库中提供了多线程库:
管理线程:
#include <thread>
thread th1;
函数 | 功能 |
---|---|
thread() | 创建线程函数:普通函数、类成员函数对象,lambda表达式 |
join | 线程阻塞,等待子线程完成、汇合 |
detach | 线程分离,失去线程所有权 |
joinable | 检查是否还拥有线程所有权,true有,false无 |
get_id | 获取线程id |
sleep_for | 休眠 |
互斥:
#include <mutex>
mutex x1;
shared_mutex x2;
mutex函数 | 功能 |
---|---|
lock | 锁定指定的互斥量mutex对象,可能会阻塞 |
unlock | 解锁 |
try_lock | 试图去lock mutex 对象,但是不会产生阻塞 |
在进入临界区之前对互斥量加锁lock
,退出临界区时对互斥量解锁unlock
;
调用该函数后,调用函数会锁定mutex
,在有些情况下调用函数会阻塞
- 如果
mutex
当前没有被任何其他线程locked
,则调用线程lock
这个mutex
对象 - 如果
mutex
目前被其他线程locked
,则当前线程阻塞直到mutex
被其他线程unlock
- 如果
mutex
目前被当前线程lock
,则会产生死锁错误。大部分情况会崩溃,因为mutex
不支持递归
try_lock
:尝试锁定互斥锁。如果互斥锁被其他线程占有,则当前调用线程也不会被阻塞,而是由该函数调用返回false;如果该互斥锁已经被当前调用线程锁住,则会产生死锁
在C++14中提供了shared_mutex解决读写锁问题,同时只能有一个写者
or 同时有多个读者
shared_mutex函数 | 功能 |
---|---|
lock_shared | 共享所有权锁定互斥,若互斥不可用则阻塞 |
unlock_shared | 解锁互斥,共享所有权 |
try_lock_shared | 尝试加共享锁,互斥不会产生阻塞 |
lock_shared共享所有者是有上限的,限制最多有多少个对象可以拥有共享所有权,如果超出上限值,阻塞执行,直至共享所有者的数目减少
- 所以shared_mutex和mutex分别构成读锁和写锁,其中lock_shared和unlock_shared进行读者的锁定与解锁,lock和unlock进行写者的锁定与解锁
可以使用lock_shared()
代替lock()
和unlock()
lock_shared函数 | 功能 |
---|---|
lock_shared | 创建即加锁,作用域结束自动解锁 |
lock_shared
lock_shared
可以理解为一个类,在构造函数中加锁,在析构函数中解锁,lock_shared
利用的是c++中的 RAII 语法(Resource Acquisition Is Initialization 资源获取就是初始化),C++标准保证任何情况下,已构造的对象最终会销毁。智能指针就是利用RAII语法。
- 通过利用使用
{ }
来调整作用域范围,使互斥量在适合的地方被解锁,如下
void threadmain(int a)
{
{
lock_guard<mutex> 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;
}
unique_lock
unique_lock
与lock_guard
类似,都可以做到自动加锁和解锁,但是lock_guard使用之后不能手动lock()
和手动unlock()
,unique_lock可以手动lock()
和手动unlock()
std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点,付出性能成本
unique_lock 的第二个参数 | |
---|---|
adopt_lock | 表示互斥量已经被Lock了,必须把互斥量提前Lock,否则会报异常 |
try_to_lock | 尝试用 mutex的lock()去锁定mutex,但是没有锁定成功,也会立即返回,并不会阻塞 |
defer_lock | 初始化了没有加锁的mutex |
使用adopt_lock
之前必须先lock
my_mutex.lock();
//要先lock(),后续才能用unique_lock的std::adopt_lock参数
std::unique_lock<std::mutex> uniquelock_test(my_mutex,std::adopt_lock);
使用try_to_lock
需要使用uniquelock_test.owns_lock()
测试是否拿到锁,因为没有锁定成功,不会阻塞
std::unique_lock<std::mutex> uniquelock_test(my_mutex,std::try_to_lock);
if (uniquelock_test.owns_lock())
{
//拿到了锁
//...
//其他处理代码
}
else
{
//没拿到锁
//...
//其他处理代码
}
使用defer_lock
之前不能加锁,并且之后要手动加锁,因为初始化的没有加锁的mutex
std::unique_lock<std::mutex> uniquelock_test(my_mutex, std::defer_lock);
//不能自己先lock,否者会出现异常
//之后需要手动 lock,unlock在析构函数中自动执行了
unique_lock可以把拥有的所有权,可以转移给其他的unique_lock对象,所有权转移,mutex可以转移,但是不能复制
mutex m;
std::unique_lock<mutex> m1(m,defer_lock);
std::unique_lock<mutex> m2(std::move(m2));//所有权转移,此时由m2来管理互斥量m
m2.lock();
m2.unlock();
m2.lock();
}
递归锁 std::recursive_mutex
允许同一线程多次锁定,最后一次unlock时释放lock;
定时递归锁 std::recursive_timed_mutex
定时的互斥锁 std::time_mutex
std::timed_mutex
比 std::mutex
多了两个成员函数:
timed_mutex函数 | 功能 |
---|---|
try_lock_for() | 函数参数表示一个时间范围,在这一段时间范围之内线程如果没有获得锁则保持阻塞;如果在此期间其他线程释放了锁,则该线程可获得该互斥锁;如果超时(指定时间范围内没有获得锁),则函数调用返回 |
try_lock_until() | 函数参数表示一个时刻,在这一时刻之前线程如果没有获得锁则保持阻塞;如果在此时刻前其他线程释放了锁,则该线程可获得该互斥锁;如果超过指定时刻没有获得锁,则函数调用返回 false |
条件变量:condition_variable
多线程通信同步使用,使其他线程等待或者通知继续
#include <condition_variable>
是用来同步线程,利用notify_one为通知,其他接收者线程调用wait(),进入阻塞状态。只要发送者线程一调用notify_one,接收者就开始行动,不再阻塞。
condition_variable函数 | 功能 |
---|---|
notify_one | 通知一个等待的线程不再阻塞,多个线程阻塞,仅通知一个,不具体哪一个 |
notify_all | 通知所有等待的线程不再阻塞 |
wait | 阻塞当前线程,直到条件变量被唤醒 |
wait_for | 阻塞当前线程,直到条件变量被唤醒,或者到定时限长 |
wait_until | 阻塞当前线程,直到条件变量被唤醒,或者到达指定时刻 |
wait函数
wait(unique_lock <mutex>&lck)
当前线程的执行会被阻塞,直到收到 notify 为止。wait(unique_lock <mutex>&lck,Predicate pred)
当前线程仅在pred=false时阻塞;如果pred=true时,不阻塞
wait()可依次拆分为三个操作:释放互斥锁、等待在条件变量上、再次获取互斥锁
mutex my_mutex;
condition_variable cond;
bool pred = false;
thread1 线程1中
{
my_mutex.lock(); ---1
cond.wait(my_mutex,pred);
while(!pred()) ---2
等待signal,并调用locker.unlock() ---3
接受signal返回并调用locker.lock() ---4
}
________________________________________
thread1 线程2中
{
my_mutex.lock(); ---5
pred = true; ---6
cond.notify_one(); ---7
//cond.notify_all();
my_mutex.unlock(); ---8
}
无论thread1还是thread2先启动,都能实现想要的效果,先看看假设thread1先启动,执行流程:1,2,3,此时thread1进入block状态等待signal,thread2无论在1,2,3的哪一步启动,启动后执行流程都是5,6,7,8,然后thread1执行4,然后进行下一轮循环,判定条件成功,跳出循环,进入下一步。
假设thread2先启动,执行流程为:5,6,7,8,此时无乱thread1在哪一步启动,执行顺序都为1,2,thread1在进行第一轮条件判断时直接跳过等待进入下一步。
加锁my_mutex.lock()
的作用:1 保护pred资源,避免多线程同时读写该资源、2 保证等待线程中pred的读操作和wait操作的原子性
Futures:
多线程异步使用