当前位置:   article > 正文

理解c++多线程编程_c++线程执行完之后自己释放

c++线程执行完之后自己释放

多线程编程

#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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 结果:

按照我们的想象,坑定是先打印很多<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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 这样结果就和我们一开始的期望一样了。

进一步思考

  • 加锁也只是针对加锁的那一个作用域,如果,我们在作用域外,再添加打印任务,会这么输出?如何解释这种现象?
#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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 先猜一下,代码块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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 结果:
<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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 跟我前面的分析完全一致~
  • 那么有趣的来了,更联系实际。
#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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 先上一个运行多次才能运行出来的结果:
<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 这个解释和上面一样,不一样的是线程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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 这个结果我一开始是有疑问的,运行了很多遍,只要线程th先抢到锁,就是这个结果。这个没问题,疑问在于当代码块1执行完后,锁自动释放,但是每次线程t2都抢不到互斥锁,也就是说执行完代码块1,代码块2抢到锁的可能性比线程t2大的多。为什么?

1)在访问共享资源时,首先申请互斥锁,如果该互斥处于开锁状态,则申请到该锁对象,并立即上锁,防止其他线程访问该资源。如果互斥锁处于锁定状态,默认阻塞当前进程。
2)阻塞(pend)就是任务释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。

  • 从上面可知,当线程th抢到锁的时候t2被阻塞了,cpu资源释放,t2要执行还要被唤醒机制唤醒,转到就绪状态,然后去排队,等待执行。对比之下,当线程th获得锁,代码块1执行完,线程th还在执行状态,这时候又一次去获得互斥锁,不需要被唤醒,直接就可以获得互斥锁,所以一定是代码块2获得互斥锁,而不是线程t2,当代码块2执行完,线程th执行完毕,释放锁,线程t2被唤醒,先执行完代码块3,在执行代码块4.

小结

  • 理解了上面的结果,对线程的工作及操作系统部分了解更深了。
  • 深入的理解了这个线程的工作机制,也涉及了函数的执行顺序,互斥锁的理解,这样对以后写多线程程序相信会有很大的好处。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/1014144
推荐阅读
相关标签
  

闽ICP备14008679号