《白话C++》第7章 Page617 多线程 线程同步-互斥体

本文介绍了C++11中的std::thread类用于创建线程,以及如何通过std::mutex实现线程同步,防止数据竞争。通过例子展示了未加锁和加锁情况下全局变量的行为变化,强调了多线程并非总是提高效率,特定情况下可能因同步开销而变慢。
摘要由CSDN通过智能技术生成

7.16.1多线程

C++11标准库提供std::thread类表示线程。而线程最大的作用就是在时间线上独立执行动作。所以构造thread对象是,入参必须是指定的动作,比如函数指针,函数对象,lambda,function对象等等。

thread类提供“join()”方法用于等待线程对象所代表的线程执行完毕。

可以看到输出,有混乱。

7.16.2 线程同步-互斥体

我们希望多线程并发输出内容可以交叉,但每一次cout输出的内容必须保持完整。

C++11提供std::mutex(互斥体)类,可以实现多线程共享的资源,在某一时间范围内,最多只有一个线程在访问。互斥体提供“lock()(上锁)”和“unlock()(解锁)”两个方法。某个线程在执行某一段代码前,如果成功使用某互斥体对象进行lock操作,后续如果其他线程也想使用某一互斥体执行加锁操作,则该线程将卡在这把锁面前,直到该互斥体调用unlock炒作。

注意,同一个互斥体对象可以在多处代码使用一旦一个线程在一个地方通过某互斥体进行加锁,则互斥体在其他代码处也将形成屏障,不允许其他线程进入。下面为foo1和foo2中cout的使用范围加上锁:

#include <iostream>
#include <thread> //线程
#include <mutex> //互斥体所在头文件

using namespace std;

std::mutex output_mutex; //定义一个全局变量

void foo1()
{
    for(int i = 0; i < 200; ++ i)
    {   /**< 互斥体提供lock(),加锁,和unlock(),解锁两个方法 */
        output_mutex.lock();      //加锁1
        cout << "a-" << i << endl;
        output_mutex.unlock();      //解锁1
    }
}

void foo2()
{
    for(int i = 0; i < 200; ++ i)
    {
        output_mutex.lock();      //加锁2
        cout << "b-" << i << endl;
        output_mutex.unlock();      //解锁2
    }
}

int main()
{
    thread trd_1(&foo1); //本次线程构造入参:一个函数地址
    thread trd_2(&foo2); //同上

    trd_1.join(); //等待trd_1所代表的线程执行完毕
    trd_2.join(); //等待trd_2多代表的线程执行完毕

    return 0;
}

加互斥锁之后的输出效果: 

再举一例,这次我们定义一个初始整数变量“global_I”,然后写一个函数折腾该数值50万次:

int global_I = 0; //其实全局变量会自动初始化
void make_change()
{
    for(int i = 0; i < 500000; ++ i)
    {
        if(global_I % 2 != 0)
        {
            global_I--;  //是奇数减1
        }
        else
        {
            global_I++; //是偶数加1
        }
    }
}

尽管每调用一次make_change(),全局变量global_I都要被折腾50万次,但折腾的结果它还是0。为了测试这一结论,先仅用单一线程调用:

int main()
{
    cout << "global_I = " << global_I << endl;
    thread trd_1(&make_change);
//    thread trd_2(&make_change);
    trd_1.join();
//    trd_2.join();
    cout << "global_I = " << global_I << endl;
    return 0;
}

接着将代码中的两行注释恢复为正常语句,再多执行几次,这下可好,global_I一会儿是-1,一会儿是0,一会儿是3 ……原因分析再后面

int main()
{
    cout << "global_I = " << global_I << endl;
    thread trd_1(&make_change);
    thread trd_2(&make_change);
    trd_1.join();
    trd_2.join();
    cout << "global_I = " << global_I << endl;
    return 0;
}

 

解决这一问题,仍然可以通过加互斥处理:

int global_I = 0; //其实全局变量会自动初始化

mutex global_I_mutex;

void make_change()
{
    for(int i = 0; i < 50000; ++ i)
    {
        global_I_mutex.lock();
        if(global_I % 2 != 0)
        {
            global_I--;  //是奇数减1
        }
        else
        {
            global_I++; //是偶数加1
        }
        global_I_mutex.unlock();
    }
}

再次编译,运行,可以感觉到程序运行变慢了,不过结果正确了。程序之所以变慢也好理解:没有加锁是,两个线程同时在调用make_change ()函数,但各跑各的;加上锁喉,“加锁区”同一时刻只能有一个线程可以经过,另一个线程在一边等着。 

【重要】:多线程处理一定比单线程快吗?

不一定,以前述代码为例,50万次等待额外损耗的时间,事实上将超出判断处理global_I的时长。可以试试直接在主线程中调用两次make_change(),其速度会远远快于加锁后处理双线程各自调用一次make_change()函数的速度。

上面,不加互斥锁,导致global_I不为0的原因分析:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值