理解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;
}

  • 结果:

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

小结

  • 理解了上面的结果,对线程的工作及操作系统部分了解更深了。
  • 深入的理解了这个线程的工作机制,也涉及了函数的执行顺序,互斥锁的理解,这样对以后写多线程程序相信会有很大的好处。
  • 13
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值