版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liunan199481/article/details/82909555
多线程编程
- 工欲善其事必先利其器CLion安装mingw并配置以支持c++11多线程编程
- 本篇博文不是主要介绍互斥锁之类的,是理解线程的执行,以便以后有把握的写多线程程序。
#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
for (int i = 0; i <5 ; ++i) {
std::cout<<"<fun1-1>\n";
}
}
void fun2(){
for (int i = 0; i < 5; ++i) {
std::cout<<"<fun2-2>\n";
}
}
int main()
{
std::thread th(fun1);
std::thread t2(fun2);
th.join();
t2.join();
return 0;
}
- 结果:
按照我们的想象,坑定是先打印很多<fun1-1>,再打印<fun2-2>
然而,你会发现每次运行结果都不一样,而且<fun1-1>与<fun2-2>交替输出。
这是为什么呢?以前学过操作系统,知道线程会竞争资源,我们这里可以把cout输出看作是一种资源,他们都要输出一段字符串,那么必定要竞争输出资源,每次谁得到这个资源,就打印自己的输出,所以就会得到这种输出现象。
- 怎么解决这种问题?--------加锁
操作系统学过,防止资源竞争造成的死锁,一般可以通过加互斥锁避免。
#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
{
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i <5 ; ++i) {
std::cout<<"<fun1>\n";
}
}
}
void fun2(){
{
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i < 5; ++i) {
std::cout<<"<fun2>\n";
}
}
}
int main()
{
std::thread th(fun1);
std::thread t2(fun2);
th.join();
t2.join();
return 0;
}
- 这样结果就和我们一开始的期望一样了。
进一步思考
- 加锁也只是针对加锁的那一个作用域,如果,我们在作用域外,再添加打印任务,会这么输出?如何解释这种现象?
#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
{//代码块1
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i <5 ; ++i) {
std::cout<<"<fun1>\n";
}
}
for (int i = 0; i <5 ; ++i) {//代码块2
std::cout<<"<fun1-1>\n";
}
}
void fun2(){
{//代码块3
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i < 5; ++i) {
std::cout<<"<fun2>\n";
}
}
for (int i = 0; i < 5; ++i) {//代码块4
std::cout<<"<fun2-2>\n";
}
}
int main()
{
std::thread th(fun1);
std::thread t2(fun2);
th.join();
t2.join();
return 0;
}
- 先猜一下,代码块1和3加锁了,所以输出完5个<fun1>才会输出<fun2>
- 结果如下:
<fun1>
<fun1>
<fun1>
<fun1>
<fun1>
<fun2>
<fun2>
<fun1-1>
<fun2>
<fun1-1>
<fun1-1>
<fun2>
<fun1-1>
<fun2>
<fun1-1>
<fun2-2>
<fun2-2>
<fun2-2>
<fun2-2>
<fun2-2>
- 1.可以看到我们的预期是对的。
- 2.从结果来看<fun1-1>与<fun2>交替输出,<fun2-2>最后输出(这点有问题,马上解释为什么)
- 那为什么会这样呢?
- 解释:
1)当创建好了2个线程th和t2,它们准备开始执行函数。
2)它们都进入函数体发现代码块1和代码块3加了互斥锁,那这样的话只能让代码块1执行完,然后释放锁,代码块3才能获得锁,它才能开始执行。所以我们预期的结果就产生了。
3)但是当代码块1执行完了,代码块2并没有加锁,这时它就和代码块3与代码4形成了竞争关系,所以会产生<fun1-1>与<fun2>交替输出的现象,理论上也可能产生<fun1-1>与<fun2-2>交替输出(所以最后输出也可能是<fun1-1>与<fun2-2>交替输出)。
4)但是函数执行是顺序的,也就是说代码块2一定在代码块1后执行、代码块4一定在代码块3前执行,也就是说不可能出现<fun1-1>在<fun1>前输出、<fun2-2>不可能出现在<fun2>之前。
- 改一下代码验证一下。
#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
{
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i <5 ; ++i) {
std::cout<<"<fun1>\n";
}
}
for (int i = 0; i <10 ; ++i) {
std::cout<<"<fun1-1>\n";
}
}
void fun2(){
{
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i < 5; ++i) {
std::cout<<"<fun2>\n";
}
}
for (int i = 0; i < 5; ++i) {
std::cout<<"<fun2-2>\n";
}
}
int main()
{
std::thread th(fun1);
std::thread t2(fun2);
th.join();
t2.join();
return 0;
}
- 结果:
<fun1>
<fun1>
<fun1>
<fun1>
<fun1>
<fun1-1>
<fun2>
<fun2>
<fun1-1>
<fun2>
<fun1-1>
<fun2>
<fun1-1>
<fun2>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun1-1>
- 跟我前面的分析完全一致~
- 那么有趣的来了,更联系实际。
#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
{
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i <5 ; ++i) {
std::cout<<"<fun1>\n";
}
}
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i <5 ; ++i) {
std::cout<<"<fun1-1>\n";
}
}
void fun2(){
{
std::unique_lock<std::mutex> lock(mutex);
for (int i = 0; i < 5; ++i) {
std::cout<<"<fun2>\n";
}
}
for (int i = 0; i < 5; ++i) {
std::cout<<"<fun2-2>\n";
}
}
int main()
{
std::thread th(fun1);
std::thread t2(fun2);
th.join();
t2.join();
return 0;
}
- 先上一个运行多次才能运行出来的结果:
<fun2>
<fun2>
<fun2>
<fun2>
<fun2>
<fun2-2>
<fun1>
<fun1>
<fun1>
<fun2-2>
<fun1>
<fun2-2>
<fun1>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun1-1>
<fun1-1>
<fun1-1>
- 这个解释和上面一样,不一样的是线程t2先获得了锁(后来居上?啊哈),这样看来上面的例子,也可能是线程t2先获得锁了。em…不过解释还是一样,对偶原理~
- 另一种多数结果:
<fun1>
<fun1>
<fun1>
<fun1-1>
<fun1-1>
<fun1-1>
<fun1-1>
<fun1-1>
<fun2>
<fun2>
<fun2>
<fun2>
<fun2>
<fun2-2>
<fun2-2>
<fun2-2>
<fun2-2>
<fun2-2>
- 这个结果我一开始是有疑问的,运行了很多遍,只要线程th先抢到锁,就是这个结果。这个没问题,疑问在于当代码块1执行完后,锁自动释放,但是每次线程t2都抢不到互斥锁,也就是说执行完代码块1,代码块2抢到锁的可能性比线程t2大的多。为什么?
1)在访问共享资源时,首先申请互斥锁,如果该互斥处于开锁状态,则申请到该锁对象,并立即上锁,防止其他线程访问该资源。如果互斥锁处于锁定状态,默认阻塞当前进程。
2)阻塞(pend)就是任务释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。
- 从上面可知,当线程th抢到锁的时候t2被阻塞了,cpu资源释放,t2要执行还要被唤醒机制唤醒,转到就绪状态,然后去排队,等待执行。对比之下,当线程th获得锁,代码块1执行完,线程th还在执行状态,这时候又一次去获得互斥锁,不需要被唤醒,直接就可以获得互斥锁,所以一定是代码块2获得互斥锁,而不是线程t2,当代码块2执行完,线程th执行完毕,释放锁,线程t2被唤醒,先执行完代码块3,在执行代码块4.
小结
- 理解了上面的结果,对线程的工作及操作系统部分了解更深了。
- 深入的理解了这个线程的工作机制,也涉及了函数的执行顺序,互斥锁的理解,这样对以后写多线程程序相信会有很大的好处。