【C++】多线程中“锁”的基本用法(一)

本文通过一个C++示例介绍了多线程编程中互斥量(Mutex)的作用,解释了无锁情况下并发访问同一变量可能导致的问题,并展示了如何使用std::mutex解决这个问题,确保线程安全。最后,总结了std::mutex的关键操作,如lock、unlock和try_lock。
摘要由CSDN通过智能技术生成

锁:mutex

锁的本质属性是为事物提供“访问保护”

在c++等高级编程语言中,锁也是用来提供“访问保护”的,被保护的是内存中的各种变量。此外,计算机领域对于“锁”有个响亮的名字——mutex(互斥量)

Mutex,互斥量,就是互斥访问的量这种东东只在多线程编程中起作用,在单线程程序中是没有什么用处的。从c++11开始,c++提供了std::mutex类型,对于多线程的加锁操作提供了很好的支持。下面看一个简单的例子,对于mutex形成一个直观的认识。

无锁的情况:mutex_no_mutex.cpp

假定有一个全局变量counter,启动两个线程,每个都对该变量自增10000次,最后输出该变量的值。 

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
#include <stdexcept>

int counter = 0;
void increase(int time) {
    for (int i = 0; i < time; i++) {
        // 当前线程休眠1毫秒
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        counter++;
    }
}

int main(int argc, char** argv) {
    std::thread t1(increase, 10000);
    std::thread t2(increase, 10000);
    t1.join();
    t2.join();
    std::cout << "counter:" << counter << std::endl;
    return 0;
}

为了显示多线程竞争导致结果不正确的现象,在每次自增操作的时候都让当前线程休眠1毫秒

如果没有多线程编程的相关经验,我们可能想当然的认为最后的counter为20000,如果这样想的话,那就大错特错了。下面是两次实际运行的结果:

[root@2d129aac5cc5 demo]# ./mutex_no_mutex
counter:19997
[root@2d129aac5cc5 demo]# ./mutex_no_mutex
counter:19996

出现上述情况的原因是:自增操作"counter++"不是原子操作,而是由多条汇编指令完成的。多个线程对同一个变量进行读写操作就会出现不可预期的操作。以上面的demo1作为例子:假定counter当前值为10,线程1读取到了10,线程2也读取到了10,分别执行自增操作,线程1和线程2分别将自增的结果写回counter,不管写入的顺序如何,counter都会是11,但是线程1和线程2分别执行了一次自增操作,我们期望的结果是12!!!!!

轮到mutex上场。

加锁的情况mutex_with_mutex.cpp

定义一个std::mutex对象用于保护counter变量。对于任意一个线程,如果想访问counter,首先要进行"加锁"操作,如果加锁成功,则进行counter的读写,读写操作完成后释放锁重要!!!); 如果“加锁”不成功,则线程阻塞,直到加锁成功。

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
#include <stdexcept>

int counter = 0;
std::mutex mtx; // 保护counter

void increase(int time) {
    for (int i = 0; i < time; i++) {
        mtx.lock();
        // 当前线程休眠1毫秒
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        counter++;
        mtx.unlock();
    }
}

int main(int argc, char** argv) {
    std::thread t1(increase, 10000);
    std::thread t2(increase, 10000);
    t1.join();
    t2.join();
    std::cout << "counter:" << counter << std::endl;
    return 0;
}

先来看几次运行结果:

[root@2d129aac5cc5 demo]# ./mutex_with_mutex
counter:20000
[root@2d129aac5cc5 demo]# ./mutex_with_mutex
counter:20000
[root@2d129aac5cc5 demo]# ./mutex_with_mutex
counter:20000

这次运行结果和我们预想的一致,原因就是“利用锁来保护共享变量”,在这里共享变量就是counter(多个线程都能对其进行访问,所以就是共享变量啦)。

简单总结一些std::mutex

  • 对于std::mutex对象,任意时刻最多允许一个线程对其进行上锁;
  • mtx.lock():调用该函数的线程尝试加锁。如果上锁不成功,即:其它线程已经上锁且未释放,则当前线程block。如果上锁成功,则执行后面的操作,操作完成后要调用mtx.unlock()释放锁,否则会导致死锁的产生
  • mtx.unlock():释放锁;
  • std::mutex还有一个操作:mtx.try_lock(),字面意思就是:“尝试上锁”,与mtx.lock()的不同点在于:如果上锁不成功,当前线程不阻塞
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值