线程
0.Linux中有POSIX标准的线程,Boost库中也支持线程(比如thread_group 和 upgrade_lock ),C++11<thread>头文件中也提供了相应的API支持我们使用线程。它们三个,你学会一个,自然触类旁通。
1.创建线程:
#include<iostream>
#include<thread>
using namespace std;
void fun(){
  cout << "fun()" << endl;
}
int main(){
  std::thread t(fun);
  t.join();
  return 0;
}编译命令:
g++ thread.cc -lpthread -std=c++11 解释:
 我们实例化一个thread对象,把fun函数地址传给它,让他去执行fun函数,在主线程中我们还让t调用了join方法,此方法会阻塞住主线程直到t执行完fun函数。也就是谁调用了join,join就会等待它执行完成。然后调用join的线程的资源会被释放。如果没有t.join();那么主线程执行完时,子线程 t 可能还没有执行完成。
 需要注意的点是:每个线程只能调用一次 join()。如果尝试对同一个线程多次调用 join(),或者对已经被 join() 或 detach() 的线程调用 join(),会抛出 std::system_error 异常:

至于detach是什么?请看下述代码:
  
#include<iostream>
#include<thread>
using namespace std;
void fun(){
  std::this_thread::sleep_for(std::chrono::seconds(3));
  cout << "fun()done" << endl;
}
void fun2(){
  std::thread t(fun);
  t.detach();
  cout << "fun2()done" << endl;
}
int main(){
  fun2();
  std::this_thread::sleep_for(std::chrono::seconds(5));
  cout << "main thread done" << endl;
  return 0;
} 
 
 调用 detach() 会使线程在后台独立运行,调用者线程不会等待它完成。有一个“脱离”的作用,而且你也不必再join它了。它会释放资源的。
如上代码:fun2作为调用者,调用了fun1函数,自己先结束了,但这不会影响fun1的执行。
2.创建线程对象时,还可以送参数进去。
3.thread没有拷贝构造函数和拷贝赋值函数,有移动构造函数和移动赋值函数。
4.在初次学习thread时,常用到两个睡眠函数。
  std::this_thread::sleep_for(std::chrono::seconds(1));//睡眠一秒模拟数据处理。
   std::this_thread::sleep_for(std::chrono::microseconds(1000)); //睡眠1000微秒也就是1秒钟。他俩可以让当前线程睡眠若干时间,方便我们模拟一些事件。
锁
0.多个线程访问同一资源时,为了保证数据的一致性,最简单的方式就是使用 mutex(互斥锁)。这是我们要讲的第一种锁。
std::mutex mtx;//定义一把锁
mtx.lock();//加锁
mtx.unlock();//解锁
 1.还有一种使用 lock_guard 自动加锁、解锁。它和mutex配合使用。原理是 RAII,和智能指针类似:实例化一个对象之后,这个对象的构造函数就是加锁,这个对象生存期到了调用析构时就解锁了。底层代码很简单。 
std::mutex mtx;
void fun()
{
{
std::lock_guard<std::mutex> lock(mtx);//lock调用构造函数,对mtx加锁。
cout << "hello world" << endl;
}
//到这里,mtx就被自动释放了。总之我们不需要显式解锁,只需要看清楚lock的生存周期。lock在自己析构的时候会解锁。
}
2.我们还可以使用 unique_lock 自动加锁、解锁。他常常与条件变量配合使用,也可以与mutex配合。 unique_lock 与 lock_guard 原理相同,但是提供了更多功能(比如可以结合条件变量使用)。 
另外:mutex::scoped_lock 其实就是 unique_lock<mutex> 的 typedef。  至于 unique_lock 和 lock_guard 详细比较,可移步 StackOverflow。 

unique_lock与条件变量配合我们在条件变量部分讲解。
条件变量
条件变量我们在Linux学习过,所以我们简单阐述:
0.什么时候用条件变量:线程 A 等待某个条件并挂起,直到线程 B 设置了这个条件,并通知条件变量,然后线程 A 被唤醒。
1.常常与unique_lock配合使用:

也就是说,你传入的参数应是一个 std::unique_lock<std::mutex> 类型的对象 。
2.条件变量被notify_all() 或者notify_one()也就是唤醒之后,之前挂起的线程就被唤醒,但是唤醒也有可能是假唤醒,因为有可能你等待的那个条件任然不满足。只是另一个线程条件满足了。所以wait一般会放在一个while循环中或者使用lambda表达式。两种是一样的:
 相当于cv.wait(lock, [] { return ready; }); 相当于:while (!ready) { cv.wait(lock); }。 都是为了防止假的唤醒。 
3.如(2)我们看到wait除了单参数的,还支持两个参数,是的,它有两种重载。如果是后者,第二个参数要求是一个函数对象,可以是仿函数也可以是Lambda表达式。
4.我们就这一个例子详细解释条件变量的相关API。
//注意,编译这种代码要用$ g++ a.cc -std=c++11 -lpthread。否则会收获:terminate called after throwing an instance of 'std::system_error'
//what(): Enable multithreading to use std::thread: Operation not permitted
#include<mutex>
#include<iostream>
#include<condition_variable>
#include<thread>
using namespace std;
std::mutex mtx;
std::condition_variable cv;
int flag = 0;
const int n = 10;
void fun1(char ch){
  std::unique_lock<std::mutex> lock(mtx);
  for (int i = 0; i < n; i++){
    cv.wait(lock, []() -> bool
            { return flag == 0; });
    cout << "fun1->" << ch << endl;
    flag = 1;
    cv.notify_all();
  }
}
void fun2(char ch){
  std::unique_lock<std::mutex> lock(mtx);
  for (int i = 0; i < n; i++){
    cv.wait(lock, []() -> bool
            { return flag == 1; });
    cout<< "fun2->" << ch << endl;
    flag = 2;
    cv.notify_all();
  }
}
void fun3(char ch){
  std::unique_lock<std::mutex> lock(mtx);
  for (int i = 0; i < n; i++){
    cv.wait(lock, []() -> bool
            { return flag == 2; });
    cout<< "fun3->" << ch << endl;
    flag = 0;
    cv.notify_all();
  }
}
int main(){
  std::thread t1(fun1, 'A');
  std::thread t2(fun2, 'B');
  std::thread t3(fun3, 'C');
  t1.join();
  t2.join();
  t3.join();
  return 0;
}上述代码依次打印ABC,打印n次。
上述代码最关键的一个点是:锁加在for循环之外,进入循环之后,判断flag,也就是是不是该我打印,如果不是,把锁释放,然后把自己挂起来。如果flag就是我要的,也就是第二个参数条件为真,那么此时他不会释放锁,然后它会继续往下执行,打印,唤醒,然后执行下一次for循环,然后又遇到了wait,此时flag不是他,然后它才释放掉锁,把自己挂起来。
注意:当wait的第二个参数是lambda表达式且值为假时,那么wait的第一个参数的锁将被解掉,然后当前线程被放在等待队列中。直到被其他线程唤醒notify。
被唤醒后,它要做的第一个事是抢刚才释放的锁,一直抢,抢到后继续判断第二个表达式的真假,若第二个返回true,那wait才算调用结束,然后执行下面代码,注意此时还是持有锁的。
上述代码,我们使用了 std::unique_lock 而不是其他种类的锁。原因:
         条件变量支持它:条件变量的wait()方法需要一个 std::unique_lock 类型的锁作为参数而不是 std::lock_guard。深层次原因是 std::unique_lock 可以在等待时自动释放锁,当条件满足时再重新获取锁。而后者则要等待对象生命周期到调用析构释放锁。 
他还可以转移所有权:unique_lock 可以通过移动语义将锁的所有权转移给其他对象,而 lock_guard 不支持这个特性。
5.再看一段代码。
  
#include<iostream>
#include<condition_variable>
#include<mutex>
#include<thread>
using namespace std;
int i = 0;
std::condition_variable cv;
std::mutex mtx;
void waits(char ch)
{
  std::unique_lock<std::mutex> lock(mtx);
  while (0 == i)
  {
    cout << "thread" << ch << " is waiting." << endl;
    cv.wait(lock);//这行代码的作用:把lock中的mtx锁释放掉。把当前线程放入等待队列(就像是等待在cv这个条件变量上)。当被其他线程唤醒之后,它先做的事设法获取锁,获取到之后到达就绪状态的,等待被调度,当cv.wait(lock)这一行执行完成中。然后上去判断0==i是否满足。不满足就进来,cout之后又wait了.....
    //等待在条件变量上与获取锁时都不是就绪状态,
  }
  cout << "thread" << ch << " is done." << endl;
}
void singals(){
  std::this_thread::sleep_for(std::chrono::microseconds(1000));
  {
    std::lock_guard<std::mutex> lock(mtx);//这里我们使用了lock_guard,因为我们只想让他打印一句,然后释放掉mtx,而不是像waits中的lock一样,不断解锁加锁,只有程序结束后才释放掉mtx。不要占用。但是还是包装的mtx,这是全局唯一的。
    cout << "first signals()" << endl;
  }
  //上面的块结束,那么lock调用自己的析构函数,析构函数会释放mtx这个互斥锁。
  cv.notify_all();//这里模拟虚假唤醒,因为i的值并没有改为1
  std::this_thread::sleep_for(std::chrono::microseconds(1000));
  {
    std::lock_guard<std::mutex> lock(mtx);
    i = 1;
    cout << "second signals()" << endl;
  }
  cv.notify_all();
  cout << "singals()done" << endl;
}
int main(){
  std::thread tha(waits,'A');
  std::thread thb(waits,'B');
  std::thread thc(waits,'C');
  std::thread thd(singals);
  tha.join();
  thb.join();
  thc.join();
  thd.join();
  return 0;
}
//上述代码中的整个while(){//...}可以换成
//cout << "thread" << ch << " is waiting." << endl;
//cv.wait(lock, []() -> bool { return i == 1; });
// 通过lambda表达式的真假判断条件是否满足,判断当前线程做什么
//总之,while循环与单参数的wait配合使用。lambda表达式和两个参数的wait配合使用。6.条件变量的wait方法如果是只有一个参数的版本。那么它在被唤醒后会做什么?
std::condition_variable cv;//定义条件变量cv
 当调用 cv.wait(lock) 时,当前线程会释放与 lock 关联的互斥锁。为什么?因为wait这个代码肯定在一个循环中,它不满足人家的条件,然后进入了函数体中,调用了wait函数。所以我们此时就把锁让出来,这样其他线程就有了获得锁的可能。避免死锁。
此时该线程被放在等待队列中,直到被notify_all() 或者notify_one()也就是唤醒之后,然后wait方法此时做的事是尝试重新获取那个互斥锁,直到获取到之后,然后因为是while循环,所以它又进行判断是否条件满足,此时如果满足,就脱离了while了,执行下面代码。不满足,说明是虚假唤醒,所以有执行了wait(),又把锁解掉,进入等待。
7.要彻底掌握条件变量,可以做一些练习:哲学家就餐问题、多个线程合作打印出1-100的值。
参考
链接









![[k8s理论知识]6.k8s调度器](https://i-blog.csdnimg.cn/direct/f5f26f9bed15489dbcb73fc3714679f5.png)



![[Python学习日记-50] Python 中的序列化模块 —— pickle 和 json](https://i-blog.csdnimg.cn/direct/b74cf09472c14246b30d103e9e507026.png)





![[JAVAEE] 线程安全问题](https://i-blog.csdnimg.cn/direct/0325e8762966485d8c32769b03cede6a.png)