ref:
https://www.cnblogs.com/wangguchangqing/p/6134635.html
https://blog.poxiao.me/p/multi-threading-in-cpp11-part-2-mutex-and-lock/
基本概念
并发:一个时间段内轮流执行多个操作。实现并发的方法包括多进程和多线程。
(两个队排同一个窗口,两队交替办理业务)
并行:两个或多个独立操作同时进行。(多个窗口)
c++线程管理
每个应用程序至少有一个进程,每个进程有一个主线程,每个线程有一个入口函数,入口函数返回线程退出。
std::thread 在头文件中声明
构造
-
default:默认构造函数创建一个空的thread执行对象
-
initalization:
template<Class Fn,class...Args>
explicit thread(Fn&& fn,Args&&...args);
初始化构造函数创建一个可被joinable的thread对象,新产生的线程调用fn函数,fn的参数为args
-
move构造函数:
thread(thread&& x)
调用成功后x不代表任何thread执行对象 -
thread不可以被拷贝构造。
线程的终止
++线程启动后,一定要在和线程相关联的thread销毁前调用t.join或t.detach确定以何种方式等待线程执行结束。++可以写一个线程类,将t.join或t.detach写在线程类的析构函数里,保证销毁前一定可以调用。
-
detach方式:
启动的线程自主在后台运行,当前代码不等待线程结束继续执行。
注意线程对局部变量的使用:离开创建线程的代码的作用域后,线程可能还在执行,这是局部变量随着作用域的完成都已销毁,如果线程继续使用局部变量的引用或指针会出现难以排查的错误。
所以以detach方式执行线程的时候要用值传递把线程访问的局部数据复制到线程的空间。 -
join方式:阻塞调用它的代码,等待启动的线程完成才会继续执行。
线程传参:
构造thread实例时传入即可,注意默认会将参数以拷贝的方式赋值到线程空间(即使参数类型是引用),因此如果希望在线程里改变对象,就要用std::ref(obj)的方式传入对象的引用。
转移线程所有权:
thread是可移动的,但是不可复制,可以通过move改变线程的所有权,灵活的决定线程在什么时候join或detach。
thread t1(f1);
thread t3(move(t1));
这时线程的所有权从t1转移到t3,调用t1.join或t1.detach会出现异常,因此thread也可以作为函数的参数或返回类型。
线程的标识:
线程的唯一标识可以通过在当前线程调用this_thread::get_id()获取,也可以通过thread的实例调用get_id()获取。
互斥锁:
保证任一时刻至多有一个线程在调用同一对象的机制。
c++11中有四个互斥对象同于同步多个线程对共享资源的访问。
-
mutex:最基本的互斥对象
-
timed_mutex:带有超时机制的互斥对象,允许等待一段时间,超时后仍未获得互斥对象的所有权时放弃等待。
-
recursive_mutex:递归互斥锁,可以被同一个线程多次加锁,以获得对互斥锁对象的多层所有权。
-
recursive_timed_mutex
进入临界区时,互斥类执行lock()加锁操作,如果这时已经被其他线程锁住,则当前线程排队等待,退出临界区时执行unlock()解锁操作。
死锁:
如果临界区中抛出异常或return,而导致没有解锁就退出,就会发生死锁。使用RAII(资源分配时初始化)方法管理互斥对象可以避免死锁。c++提供了一些互斥对象管理类模板:
- lock_guard:严格基于作用域的锁管理类模板,构造是是否加锁是可选的(不加锁时假定当前线程已获得锁的所有权)析构时自动释放锁,所有权不可转移。对象生存期内不允许手动加锁和释放锁。
- unique_lock:更加灵活,构造时是否加锁是可选的,对象析构时如果持有锁会自动释放锁。所有权可以转移,生命周期内允许手动加锁和释放锁。
//轮流向set中插入数字
std::mutex mt;
std::set<int> intSet;
auto f= [&intSet,&mt](){
try{
for(std::size_t i=0;i!=100000;++i)
{
std::lock_guard<std::mutex> lock(mt);
intSet.insert(1);
}
}catch(...){}
};
std::thread td1(f),td2(f);
td1.join();
td2.join();
同时:++为了避免死锁,在多个线程中对任意两个互斥对象加锁时应保证其先后顺序是一致的。++
加锁策略:
构造时是否加锁是可选的,其加锁策略有:
- defer_lock:不请求锁
- try_to_lock:尝试请求锁但不阻塞线程,锁不可用时也会立即返回
- adopt_lock:假定当前线程已经获得互斥对象的所有权,所以不再请求锁。
lock_guard使用的是第三种策略。
boost库多线程:
mutex类:
- 独占式互斥量:mutex、try_mutex(为了兼容旧版本)、timed_mutex(提供超时锁定功能)
- 递归式互斥量:recursive_mutex(可以多次锁定,相应地也要多次解锁)、recursive_try_mutex(recursive_mutex 的同义词)、recursive_timed_mutex
- 共享式互斥量
为了避免死锁,boost设计了scoped_lock模式,在构造和析构函数中对mutex加锁或解锁,c++语言规范保证,即使有异常抛出也会调用析构函数。
scoped_lock是一种独占锁:
typedef unique_lock scoped_lock;
读写锁的实现
typedef boost::shared_lock<boost::shared_mutex> readLock;
typedef boost::unique_lock<boost::shared_mutex> writeLock;
boost::shared_mutex rwmutex;
void readOnly()
{
readLock rdlock(rwmutex);
// do something
}
void writeOnly()
{
writeLock wtlock(rwmutex);
// do something
}
对同一个rwmutex,线程可以同时有多个readLock,这些readLock会阻塞任意一个企图获得writeLock的线程,直到所有的readLock对象都析构。如果writeLock首先获得了rwmutex,那么它会阻塞任意一个企图在rwmutex上获得readLock或者writeLock的线程。