C++多线程学习(二):多线程通信和锁

news/2024/5/19 7:16:49 标签: c++, 互斥锁, 超时锁, 递归锁, 竞争锁, mutex, 多线程

参考引用

  • C++11 14 17 20 多线程从原理到线程池实战
  • 代码运行环境:Visual Studio 2019

1. 多线程状态

1.1 线程状态说明

  • 初始化 (lnit):该线程正在被创建
  • 就绪 (Ready):该线程在就绪列表中,等待 CPU 调度
  • 运行 (Running):该线程正在运行
  • 阻塞 (Blocked):该线程被阻塞挂起,Blocked 状态包括
    • pend (锁、事件、信号量等阻塞)
    • suspend (主动 pend)
    • delay (延时阻塞)
    • pendtime (因为锁、事件、信号量时间等超时)
  • 退出 (Exit):该线程运行结束,等待父线程回收其控制块资源
    • 告诉操作系统把该线程相关资源释放,不包含堆中的资源释放

在这里插入图片描述

1.2 竞争状态和临界区

  • 竞争状态 (Race Condition)
  • 临界区 (Critical Section)
    • 读写共享数据的代码片段(lock 和 unlock 之间的代码)

避免竞争状态策略,对临界区进行保护,同时只能有一个线程进入临界区

2. 互斥体和锁

2.1 互斥锁

  • thread_mutex.cpp
    #include <iostream>
    #include <thread>
    #include <string>
    #include <mutex>
    
    using namespace std;
    
    static mutex mux;
    
    void TestThread() {
        for (;;)
        {	
            // 获取锁资源,如果没有则阻塞等待(一次只能有一个线程拿到锁)
            // 拿锁的原则:尽晚申请、尽早释放
            //mux.lock();           // 拿锁方式一 
            if (!mux.try_lock()) {  // 拿锁方式二:可以看到多个进程在竞争拿锁的情况
                cout << "." << flush;
                this_thread::sleep_for(100ms);
        
                continue;
            }
        
            // 业务代码
            cout << "=========" << endl;
            cout << "Test 001" << endl;
            cout << "Test 002" << endl;
            cout << "Test 003" << endl;
            cout << "=========" << endl;
    
            mux.unlock();  // 如果忘记释放锁,则会导致死锁,所有线程都在等待
            this_thread::sleep_for(1000ms);
        }
    }
    
    int main(int argc, char* argv[]) {
        // 同时创建 10 个线程
        for (int i = 0; i < 10; i++) {
            thread th(TestThread);
            th.detach();
        }
        
        getchar();
        
        return 0;
    }
    

2.2 线程抢占不到资源

  • thread_mutex2.cpp
    #include <iostream>
    #include <thread>
    #include <string>
    #include <mutex>
    
    using namespace std;
    
    static mutex mux;
    
    void ThreadMainMux(int i) {
        for (;;)
        {	
            mux.lock();
            cout << i << "[in]" << endl;
            this_thread::sleep_for(1000ms);
            mux.unlock();
            // 防止 unlock() 还未释放完全就进入下一个 lock(),导致其他线程拿不到锁
            this_thread::sleep_for(1ms);
        }
    }
    
    int main(int argc, char* argv[]) {
        // 同时创建 3 个线程
        for (int i = 0; i < 3; i++) {
            thread th(ThreadMainMux, i + 1);
            th.detach();
        }
    
        getchar();
    
        return 0;
    }
    

mutex__115">2.3 超时锁 timed_mutex 应用

  • 可以记录锁的获取情况,多次超时,可以记录日志,获取错误情况,避免长时间死锁
  • timed_mutex.cpp
    #include <iostream>
    #include <thread>
    #include <string>
    #include <mutex>
    
    using namespace std;
    
    timed_mutex tmux;  // 支持超时的互斥锁
    
    void ThreadMainTime(int i) {
        for (;;)
        {	
            if (!tmux.try_lock_for(chrono::milliseconds(500))) {
                cout << i << "[try_lock_for timeout]" << endl;
                continue;
            }
            cout << i << "[in]" << endl;
            this_thread::sleep_for(2000ms);
            tmux.unlock();
            // 防止 unlock() 还未释放完全就进入下一个 lock(),导致其他线程拿不到锁
            this_thread::sleep_for(1ms);
        }
    }
    
    int main(int argc, char* argv[]) {
        getchar();
    
        // 同时创建 3 个线程
        for (int i = 0; i < 3; i++) {
            thread th(ThreadMainTime, i + 1);
            th.detach();
        }
    
        getchar();
    
        return 0;
    }
    

mutex_158">2.4 递归锁 recursive_mutex(可重入)

  • 同一个线程中的同一把锁可以锁多次,避免一些不必要的死锁
  • 组合业务:用到同一个锁
  • recursive_mutex.cpp
    #include <iostream>
    #include <thread>
    #include <string>
    #include <mutex>
    
    using namespace std;
    
    recursive_mutex rmux;  // 支持可重入的互斥锁
    
    void Task1() {
        rmux.lock();
        cout << "task1 [in]" << endl;
        rmux.unlock();
    }
    
    void Task2() {
        rmux.lock();
        cout << "task2 [in]" << endl;
        rmux.unlock();
    }
    
    void ThreadMainRec(int i) {
        for (;;)
        {
            // 加锁几次对应的也要解锁几次
            rmux.lock();
            Task1();
            cout << i << "[in]" << endl;
            this_thread::sleep_for(2000ms);
            Task2();
            rmux.unlock();
            this_thread::sleep_for(1ms);
        }
    }
    
    int main(int argc, char* argv[]) {
        // 同时创建 3 个线程
        for (int i = 0; i < 3; i++) {
            thread th(ThreadMainRec, i + 1);
            th.detach();
        }
        
        getchar();
        
        return 0;
    }
    

mutex_211">2.5 共享锁 shared_mutex(解决读写问题)

  • c++14 共享超时互斥锁 shared_timed_mutex
  • 如果只有写时需要互斥,读取时不需要,用普通的锁的话如何做
  • 按照如下代码,读取只能有一个线程进入,在很多业务场景中,没有充分利用 CPU 资源

在这里插入图片描述

  • shared_mutex.cpp
    #include <iostream>
    #include <thread>
    #include <string>
    #include <mutex>
    #include <shared_mutex>
    
    using namespace std;
    
    shared_timed_mutex stmux;  // 支持可重入的共享锁 C++14
    
    // 读取线程
    void ThreadRead(int i) {
        for (;;)
        {
            stmux.lock_shared();
            cout << i << " Read" << endl;
            this_thread::sleep_for(500ms);
            stmux.unlock_shared();
    
            this_thread::sleep_for(1ms);
        }
    }
    
    // 写入线程
    void ThreadWrite(int i) {
        for (;;)
        {
            stmux.lock_shared();  // 只要没有锁定互斥锁,共享锁都是立即返回
            // 读取数据
            stmux.unlock_shared();
    
            // 互斥锁 写入(同时只能一个线程写入),共享锁和互斥锁都不能进入
            stmux.lock();  
            cout << i << " Write" << endl;
            this_thread::sleep_for(300ms);
            stmux.unlock();
    
            this_thread::sleep_for(1ms);
        }
    }
    
    int main(int argc, char* argv[]) {
        for (int i = 0; i < 3; i++) {
            thread th(ThreadWrite, i + 1);
            th.detach();
        }
    
        for (int i = 0; i < 3; i++) {
            thread th(ThreadRead, i + 1);
            th.detach();
        }
    
        getchar();
    
        return 0;
    }
    

http://www.niftyadmin.cn/n/5204448.html

相关文章

PTA-6-45 工厂设计模式-运输工具

题目如下&#xff1a; 工厂类用于根据客户提交的需求生产产品&#xff08;火车、汽车或拖拉机&#xff09;。火车类有两个子类属性&#xff1a;车次和节数。拖拉机类有1个子类方法耕地&#xff0c;方法只需简单输出“拖拉机在耕地”。为了简化程序设计&#xff0c;所有…

【黑马甄选离线数仓day01_项目介绍与环境准备】

1. 行业背景 1.1 电商发展历史 电商1.0: 初创阶段20世纪90年代&#xff0c;电商行业刚刚兴起&#xff0c;主要以B2C模式为主&#xff0c;如亚马逊、eBay等 ​ 电商2.0: 发展阶段21世纪初&#xff0c;电商行业进入了快速发展阶段&#xff0c;出现了淘宝、京东等大型电商平台&a…

音视频项目—基于FFmpeg和SDL的音视频播放器解析(十九)

介绍 在本系列&#xff0c;我打算花大篇幅讲解我的 gitee 项目音视频播放器&#xff0c;在这个项目&#xff0c;您可以学到音视频解封装&#xff0c;解码&#xff0c;SDL渲染相关的知识。您对源代码感兴趣的话&#xff0c;请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本…

python BDD 的相关概念

在Python 语言中进行BDD的规格和测试文件的编写的时候&#xff0c;常常会遇到下面的概念&#xff1a; Fixture : 测试设施。设定测试环境的预设状态或值的机制。Background&#xff1a; 背景。所有场景的公共部分。Scenario&#xff1a; 场景。Given &#xff1a; 前置条件Whe…

IOS+Appium+Python自动化全实战教程

由于公司的产品坐落于不同的平台&#xff0c;如ios、mac、Android、windows、web。因此每次有新需求的时候&#xff0c;开发结束后&#xff0c;留给测试的时间也不多。此外&#xff0c;一些新的功能实现&#xff0c;偶尔会影响其他的模块功能正常的使用。 网上的ios自动化方面的…

QTableWidget——编辑单元格

文章目录 前言熟悉QTableWiget&#xff0c;通过实现单元格的合并、拆分、通过编辑界面实现表格内容及属性的配置、实现表格的粘贴复制功能熟悉QTableWiget的属性 一、[单元格的合并、拆分](https://blog.csdn.net/qq_15672897/article/details/134476530?spm1001.2014.3001.55…

PRD学习

产品经理零基础入门&#xff08;五&#xff09;产品需求文档PRD&#xff08;全16集&#xff09;_哔哩哔哩_bilibili 1. PRD的2种表现形式 ① RP格式 &#xff08;1&#xff09;全局说明 ② 文档格式 2. 交互说明撰写 ① 维度 ② 步骤 ③ 规则 &#xff08;1&#xff09;单位…

【C++】泛型编程 ⑫ ( 类模板 static 关键字 | 类模板 static 静态成员 | 类模板使用流程 )

文章目录 一、类模板使用流程1、类模板 定义流程2、类模板 使用3、类模板 函数 外部实现 二、类模板 static 关键字1、类模板 static 静态成员2、类模板 static 关键字 用法3、完整代码示例 将 类模板 函数声明 与 函数实现 分开进行编码 , 有 三种 方式 : 类模板 的 函数声明…