C++ 多线程 互斥量(mutex)与锁(lock)

引自C++ 多线程 互斥量(mutex)与锁(lock)_mutex 上下文切换-CSDN博客

一、基本概念

在多线程环境中,有多个线程竞争同一个公共资源,就很容易引发线程安全的问题。因此就需要引入锁的机制,来保证任意时候只有一个线程在访问公共资源。

互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。

互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。

二、使用方法

包含头文件#include <mutex>

2.1 mutex.lock(),unlock()

步骤:1.lock(),2.操作共享数据,3.unlock()。

lock()和unlock()要成对使用,不能重复上锁和解锁。本质就是lock~unlock之间的程序(数据)不会同时调用、修改。

复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

list<int> test_list;

mutex my_mutex;
void in_list(){
    for(int num=0;num<1000000000000000000;num++){
        my_mutex.lock();
        cout<<"插入数据: "<<num<<endl;
        test_list.push_back(num);
        my_mutex.unlock();
    }

}

void out_list(){

    for(int num=0;num<1000000000000000000; ++num){
        my_mutex.lock();
        if(!test_list.empty()){
            int tmp = test_list.front();
            test_list.pop_front();
            cout<<"取出数据:"<<tmp<<endl;

        }
        my_mutex.unlock();

    }
}
int main()
{


    thread in_thread(in_list);
    thread out_thread(out_list);
    in_thread.join();
    out_thread.join();
    cout << "Hello World!" << endl;

    return 0;
}

2.2 std::lock_guard类模板

lock_guard构造函数执行了mutex::lock(),在作用域结束时,自动调用析构函数,执行mutex::unlock()

复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

list<int> test_list;

mutex my_mutex;
void in_list(){
    for(int num=0;num<1000000000000000000;num++){
        std::lock_guard<std::mutex> my_guard(my_mutex);//要先定义好my_mutex
        cout<<"插入数据:"<<num<<endl;
        test_list.push_back(num);
    }
}

void out_list(){
    for(int num=0;num<1000000000000000000; ++num){
        std::lock_guard<std::mutex> my_guard(my_mutex);
        if(!test_list.empty()){
            int tmp = test_list.front();
            test_list.pop_front();
            cout<<"取出数据:"<<tmp<<endl;
        }
    }
}
int main(){
    thread in_thread(in_list);
    thread out_thread(out_list);
    in_thread.join();
    out_thread.join();
    cout << "Hello World!" << endl;

    return 0;
}

2.2.1 std::lock_guard的std::adopt_lock参数

复制代码
std::lock_guard<std::mutex> my_guard(my_mutex,std::adopt_lock);

加入adopt_lock后,再调用lock_guard的构造函数时,不再进行lock();

adopt_guard为结构体对象,起一个标记作用,表示这个互斥量已经lock(),不需要再lock()。

2.3 std::unique_lock函数模板

unique_lock相比于lock_guard,都是基于RAII思想的,也支持std::lock_guard的功能,但是区别在于它提供更多的成员函数,比如:try_lock(), try_lock_for()...使用更加灵活,并且可以和condiction_variable一起使用控制线程同步。但是效率差一点,内存占用多一点。

2.3.1 unique_lock的第二个参数

1) std::adopt_lock:

  • 表示这个互斥量已经被lock(),即不需要在构造函数中lock这个互斥量了。
  • 前提:必须提前lock
  • lock_guard中也可以用这个参数

2) std::try_to_lock:

  • 尝试用mutx的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里,但也不能操作保护的数据(防止异常),只能操作不受保护的数据;
  • 使用try_to_lock的原因是防止其他的线程锁定mutex太长时间,导致本线程一直阻塞在lock这个地方
  • 前提:不能提前lock();
  • unique_lock.owns_locks()方法判断是否拿到锁,如拿到返回true

3) std::defer_lock:

  • 加上defer_lock是始化了一个没有加锁的mutex
  • 不给它加锁的目的是以后可以调用后面提到的unique_lock的一些方法
  • 前提:不能提前lock

三、死锁

3.1 发生原因

死锁至少有两个互斥量mutex1,mutex2。

线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。

线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1。

此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。

复制代码
#include<iostream>
#include<thread>
#include<mutex>

std::mutex m1, m2;

void func_1(){
    for(int i=0; i<50; i++){
        m1.lock();
        m2.lock();
        m1.unlock();
        m2.unlock();
    }
}
void func_2(){
    for(int i=0; i<50; i++){
        m2.lock();
        m1.lock();
        m2.unlock();
        m1.unlock();
    }
}

int main(){
    std::thread t1(func_1);
    std::thread t2(func_2);
    t1.join();
    t2.join();
    std::cout<<"over"<<std::endl;
    return 0;
}

3.2 解决办法

只要保证多个互斥量上锁的顺序一样就不会造成死锁。

即将func_2中上锁的顺序也改成m1.lock(); m2.lock(); m1.unlock(); m2.unlock();

这样谁先获取到m1的所有权,就会进而先获取到m2的所有权,然后释放m1和m2.

相关推荐
白帽攻防录5 小时前
SRC 挖洞:PostScript 打印机驱动双 RCE,打印服务器怎么变成内网跳板
开发语言·网络安全·php·内网安全
十年Java程序媛6 小时前
Java枚举Enum完整教程|替代静态常量,开发与面试要点
java·开发语言·spring boot·后端·面试
2603_965148116 小时前
电商选品调研:通过关键词搜索API获取市场热销商品,结合商品详情API分析竞品卖点与定价
开发语言·数据库·人工智能·python·信息可视化
所以遗憾是什么呢?7 小时前
【题解】Educational Codeforces Round 195 (Rated for Div. 2) ABCDE
c++·动态规划·acm·icpc·ccpc·xcpc·codeforecs
xixiaoyunya7 小时前
Python 异步编程入门:用 asyncio 轻松实现并发任务
开发语言·python·php
学逆向的7 小时前
C 循环结构的执行效率分析
c语言·开发语言·数据结构·网络安全
小坏讲微服务7 小时前
刚发布一个月的JDK 27,为什么我建议你立刻升级?
java·开发语言·spring boot·spring·架构·求职招聘·springboot4
Lhan.zzZ8 小时前
Qt WebEngineView 在 Linux 上无法加载网页:从“看不见 HTML“到“禁用沙箱“
linux·c++·qt
打工仔折腾 AI9 小时前
用 Dashlet 把极空间 NAS 服务入口整理成统一导航首页的实践
java·开发语言·人工智能·后端·python·rpc·ai agent 实战
MATLAB代码顾问9 小时前
Simulink PI 参数扫描与批量仿真完整 MATLAB 代码
开发语言·matlab