C++11 多线程
c++多线程
没有多线程的时候,在执行download的时候会卡住
在引入多线程之后,就可以在另一个线程中执行download,而在主线程中执行interact
join函数的作用
没有join的话,子线程可能会由于主线程退出而被迫退出
所以可以调用t1.join()等待子线程结束,主线程才结束
detach函数
当一个线程在函数中创建时,当函数结束时,该线程也会被解构,那么这个时候该线程还没被执行完,错误了
所以这个时候可以用detach函数(也可以在函数体里头join),detach函数与join的区别thread对象可以想象成一根指针,那么当调用detach的时候,该thread对象就是nonjoinable了,就不会报exception了
但是这个时候仍然存在:主进程退出,相关的所有线程都会退出的情况,这时候可以创建一个线程池(在main函数里join全部的线程)
C++20自动join所有子线程
一个线程调用joinable()函数判断该线程是否可以被join或者detach
异步
std::async产生一个带返回值的thread
std::async的get像是带返回值的join,而wait像是join
wait_for与status结合判断线程是否执行结束
用std::launch::deferred实现惰性求值
std::async 的底层实现:std::promise
std::shared_future
互斥量
std::lock_guard的上🔒与解🔒符合RAII思想,离开for的花括号就会自动解锁,其实这个lock_guard的实现就是一个class,在每次进入for的{}时候创建一个object,那么调用构造函数,其中就是mutex.lock(),而退出{}时,调用析构,析构便会调用mutex.unlock()
std::unique_lock自由度更高
自由度高到甚至可以延迟上🔒
多个对象可以多个🔒
try_lock上🔒失败不等待
try_lock_for是设置等待时间
unique_lock可以用**std::try_to_lock **作为参数
std::adopt_lock
鸭子类型
死锁
ab型死锁
手动解决死锁
std::lock 解决死锁
符合RAII思想的std::scoped_lock
aa型死锁
手动解决aa型死锁
std::recursive_mutex标准库帮忙解决aa型死锁
线程安全的数据结构
封装一个安全的vector
mutable使得变量可以在const函数中修改
读写🔒
shared_mutex既可以调用lock()也可以调用lock_shared()
符合RAII思想的shared_mutex
条件变量
- 为什么条件变量的wait函数的第一个参数是mutex?以及可以看下这个链接,惊群效spurious wakeups
这里的cv.wait()表示没拿到cv,也就是假定共享变量一开始是0,
cv.wait()在cv的等待队列中塞入一个线程,可以发现 std::condition_variable 必须和 std::unique_lockstd::mutex 一起用
这就是为什么 wait() 需要一个 unique_lock 作为参数,因为要保证多个线程被唤醒时,只有一个能够被启动。如果不需要,在 wait() 返回后调用 lck.unlock() 即可
甚至可以有一个额外的参数,来达到继续等到的目的,可以看到,第二次notify_one才把thread t1唤醒
notify_all可以唤醒多个等待者
原子操作
一条语句会被拆分为三条汇编
- 加🔒暴力解决问题,但是慢
- 用atomic解决
但是counter = counter + 1
不是原子的,因为编译器看不出来前后两个counter是同一个对象
还可以调用与+=
等价的函数fetch_add
追加多个是啥意思?是指追加到当前元素的后n个位置
atomic变量的exchange操作
compare_exchange_strong,
为啥CAS操作都是这样的,还要去改变别人传进来的值?难道是为了第二次获取🔒的时候能够获取到吗