C++11多线程编程 二:多线程通信,同步,锁

news2024/9/23 13:23:45

C++11多线程编程 一:多线程概述

C++11多线程编程 二:多线程通信,同步,锁

C++11多线程编程 三:锁资源管理和条件变量 


2.1 多线程的状态及其切换流程分析

线程状态说明:

        初始化(Init):该线程正在被创建。(就是创建thread对象并设置好回调函数,该线程就处在初始化状态,初始化线程的内存空间啊等,这部分其实代码干预部分不多,也就是从初始化,到就绪态之间其实是有一个时间消耗的,这就是为什么后面使用线程池来做一个处理,来减少时间消耗,当各种内存准备好之后,就变成就绪态了)
        就绪(Ready):不代表立刻就能运行了,该线程在就绪列表中,等待 CPU 调度。
        运行(Running):该线程正在运行。被CPU调度到了。
        阻塞(Blocked):该线程被阻塞挂起。Blocked 状态包括:pend(锁、 事件、信号量等阻塞)、suspend(主动 pend)、delay(延时阻塞)、 pendtime(因为锁、事件、信号量时间等超时等待)。阻塞态就是,CPU的调度已经不再这里了,放弃CPU的调度,不浪费资源。
        退出(Exit):该线程运行结束,等待父线程回收其控制块资源。


2.2 竞争状态和临界区介绍 互斥锁mutex代码

竞争状态(Race Condition): 多线程同时读写共享数据
临界区(Critical Section): 读写共享数据的代码片段
要避免竞争状态策略, 对临界区进行保护,同时只能有一个线程进入临界区。
 
例子:期望进行整块的输出,如下,
==============================
test 001
test 002
test 003
==============================
一共创建10个线程,那么我期望每个线程输出一个这样的整块屏幕输出。写如下代码:

但是最终的结果却不是想象的那样的,而出现了乱码。

这时候需要加一个mutex的锁(mutex是一个互斥锁),需要包含头文件:#include <mutex>
static mutex mux; mux叫做互斥变量。资源被上锁后,其他线程相当于是排队等待-阻塞
mux.lock是操作系统层面的锁,mux互斥锁只有这一个,线程主动过来处理的时候必须先抢占锁资源,所以线程它是一边抢占CPU资源一边抢占锁资源。

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
//Linux -lpthread
using namespace std;
static mutex mux;
void TestThread()
{
    for (;;)
    {
        //获取锁资源,如果没有则阻塞等待
        //mux.lock(); //
        if (!mux.try_lock())  //try_lock()返回TRUE表示获得锁资源
        {
            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[])
{
    for (int i = 0; i < 10; i++)
    {
        thread th(TestThread);
        th.detach();
    }
    getchar();
    return 0;
}

 

这样就不会出现错误了,每次都是整块的输出,而不再有乱码了。


2.3 互斥锁的坑 线程抢占不到资源原因

理想状态,当一个线程释放锁资源之后,后面的线程会排队获取锁资源,但是实际上有时会出现一个线程始终占领着这个资源,其他线程排队永远获取不到资源的情况。

你会发现结果就是一直总是这一个或者两个线程进入,而不是理想的所有的线程都能得到展示,并不是我们想要的结果,原因如下:

        这段代码中,线程1获得锁资源的时候,线程2和线程3处于阻塞态,当线程1解锁的时候,按道理2号3号线程应该有一个立即获得锁资源的,但是对于线程1来说,当它解锁的时候,又重新进入了锁,也就是解锁后自己又重新申请了锁,这个锁是操作系统内核来判断,这个锁资源有没有被占用掉,当线程1解锁后,它的内存资源当然不是立即就被释放的,因为我们的操作系统不是实时操作系统,从解锁再到锁之间可能就是微秒级别的,而CPU调度肯定是过一段时间探测一下这个资源,当线程1解锁后立即又进入了锁,操作系统来不及反应,操作系统会认为是线程1又抢到了锁资源,实际上不是,而是因为线程1的资源还没有来得及释放就又重新进入锁了,所以它没有排队就再次进入了,所以这就是坑的所在,因此在解锁和锁之前要加一个延时,给操作系统做一个释放的时间。

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
//Linux -lpthread
using namespace std;
static mutex mux;
 
void ThreadMainMux(int i)
{
    for (;;)
    {
        mux.lock();
        cout << i << "[in]" << endl;
        this_thread::sleep_for(1000ms);
        mux.unlock();
        this_thread::sleep_for(1ms);
    }
}
int main(int argc, char* argv[])
{
    for (int i = 0; i < 3; i++)
    {
        thread th(ThreadMainMux, i + 1);
        th.detach();
    }
 
    getchar();
    return 0;
}


2.4 超时锁timed_mutex(避免长时间死锁)和可重入锁

        mutex默认没有超时的,而有线程占用的情况下,其他线程是一直处于阻塞状态的,这种方式代码简洁,但是为后期的代码调试增加难度,比如说我们不小心在代码当中写了一个死锁,那你在调试当中你怎么去找到这个死锁呢?每次锁之前记录一下日志,看有没有进去,这样的调试成本比较大,不容易发现,只要加了timed就支持超时。

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
//Linux -lpthread
using namespace std;
timed_mutex tmux;
 
void ThreadMainTime(int i)
{
    for (;;)
    {
        if (!tmux.try_lock_for(chrono::milliseconds(500)))  //等待时间超过500ms就超时了
        {
            cout << i << "[try_lock_for timeout]" << endl;
            continue;
        }
        cout << i << "[in]" << endl;
        this_thread::sleep_for(2000ms);  //假设要处理的业务的持续时间
        tmux.unlock();
        this_thread::sleep_for(1ms);     //防止某一个线程一直占用这个锁资源
    }
}
 
int main(int argc, char* argv[])
{
    for (int i = 0; i < 3; i++)
    {
        thread th(ThreadMainTime, i + 1);
        th.detach();
    }
    getchar();
    return 0;
}

        很多业务可能会用到同一个锁,就会出现同一个锁被锁多次的情况,如果是普通的锁的话(mutex锁),第二次锁的时候会抛出异常,若没有捕获异常,那么程序就会崩溃掉,而这个递归锁可以进行多次上锁,他会把当前线程的锁计数加一,还是处于锁的状态不会改变,有多少次lock就会有多少次unlock,直到计数变成零的时候才真正释放,可以避免不必要的死锁。 

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
//Linux -lpthread
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[])
{
    for (int i = 0; i < 3; i++)
    {
        thread th(ThreadMainRec, i + 1);
        th.detach();
    }
    getchar();
    return 0;
}

2.5 共享锁shared_mutex解决读写问题

        当前线程在写数据的时候需要互斥,就是其他线程既不能写也不能读。当前线程在读的时候,其他线程只能读,不能写。
        这里面就涉及到两个锁,一个读的锁,一个写的锁,
若这个线程只读的话,那我们就用一个只读的锁就可以了,但是这个线程里面涉及到修改的时候,需要先拿到读的锁,然后再去获得写的锁,然后再修改,修改完之后再释放,用这种方式做一个共享。
        共享锁当中包含两个锁,一个是共享锁,一个是互斥锁,只要没有人锁定互斥锁,共享锁都是立即返回的,只要有人锁定了互斥锁,共享锁不能进,其他的互斥锁也不能进。
c++14 共享超时互斥锁 shared_timed_mutex(默认一般值支持到C++14)
c++17 共享互斥 shared_mutex
如果只有写时需要互斥,读取时不需要,用普通的锁的话如何做
按照如下代码,读取只能有一个线程进入,在很多业务场景中,没有充分利用 cpu 资源。

        原理是,如果有一个线程在写,其他所有线程既不能读也不能写,如果说有一个线程在读,其他线程都可以读,但不能写,必须等待所有的读线程读完之后才能写,这样就确保了资源不会被多人写而出现错误。
        这就是我们要用的共享锁。读取锁一定要先释放,读取锁如果锁住的话,互斥锁也是进不去的。互斥锁一旦进入,其他的所有读取线程都在等待。其他线程的写入锁也在等待,也既同时只能有一个线程在写入。

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
#include <shared_mutex>
//Linux -lpthread
using namespace std;
//c++17  共享锁
//shared_mutex smux;
 
//c++14  共享锁 
shared_timed_mutex stmux;
 
void ThreadRead(int i)
{
    for (;;)
    {
        //stmux.lock_shared();共享锁,只要对方没有把互斥锁锁住,共享锁大家都可以进去。
        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.coloradmin.cn/o/372886.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

javaEE 初阶 — 网络层中 IP 协议 的报文结构

文章目录IP 协议报文4位版本号4位首部长度8位服务类型16位总长度&#xff08;字节数&#xff09;8位生存时间&#xff08;TTL&#xff09;与 8位协议16位首部校验和32位源 IP 地址与32位目标 IP 地址动态分配的 IP 地址NAT 网络地址转换IPv6IP 协议报文 4位版本号 这里的 IP 协…

图表示学习+对比学习入门必看:DGI

来源&#xff1a;投稿 作者&#xff1a;kon 编辑&#xff1a;学姐 前言 众所周知&#xff0c;火热的对比学习不仅在CV取得了很多成果&#xff0c;也在NLP、推荐等领域大放异彩。自然的&#xff0c;有人将对比学习引入了图表示学习领域&#xff0c;利用图本身的结构与结点自身的…

14.微服务SpringCloud

一、基本概念 Spring Cloud 被称为构建分布式微服务系统的“全家桶”&#xff0c;它并不是某一门技术&#xff0c;而是一系列微服务解决方案或框架的有序集合。它将市面上成熟的、经过验证的微服务框架整合起来&#xff0c;并通过 Spring Boot 的思想进行再封装&#xff0c;屏蔽…

【Servlet篇】Response对象详细解读

文章目录Response 继承体系Response 设置响应数据设置响应行数据设置响应头数据设置响应体数据Response 重定向Response 响应字符数据Response 响应字节数据Response 继承体系 前面说到&#xff0c;我们使用 Request 对象来获取请求数据&#xff0c;使用 Response 对象来设置响…

Pyinstaller 打包EXE(七) 百篇文章学PyQT

本文章是百篇文章学PyQT6的第七篇&#xff0c;本文讲述如何使用Pyinstaller打包UI界面和代码&#xff0c;将程序打包成EXE来更为方便的进行部署&#xff0c;在写博客和学习的过程中会遇到很多问题&#xff0c;例如&#xff1a;PyQT6在网上很多博客都是PyQT5、或者PyQT4大部分都…

Could not extract response: no suitable HttpMessageConverter

版本&#xff1a;spring-cloud-openfeign-core-2.1.1.RELEASE.jar&#xff0c;spring-webmvc-5.1.14.RELEASE.jar&#xff0c;jetty-server-9.4.41.v20210516.jar&#xff0c;tomcat-embed-core-9.0.48.jar 问题背景 生产服务请求下游服务时偶发抛出下面的异常&#xff0c;下…

git入门

目录 1. git简介 1.1 git是什么 1.2 git与svn的区别 2. github 2.1 创建仓库 2.2 删除仓库 2.3 新建文件及文件夹 3. git的基本操作 3.1 配置账户及邮箱 3.2 git文件状态与工作区域 3.3 常用命令 3.4 克隆&#xff08;clone&#xff09; 3.5 查看git仓库的状态 3.…

[音视频] BMP 图片格式分析

BMP 格式是什么 BMP&#xff08;Bitmap&#xff09;是一种常见的无损位图图像文件格式&#xff0c;是Windows操作系统中最早使用的图像格式之一&#xff0c;也是目前很多应用程序所使用的标准图像格式之一。 整体结构图&#xff0c;如下图所示 格式 BMP文件格式有多个版本&a…

QT中级(5)多线程读取一个文件,并在另一个文件夹中合成这个文件(1)

1 先实现一个简单程序 1.1 功能 用户可以输入一个源文件的路径和目标路径点击开始&#xff0c;程序启动读取和合成合成进度可见、合成步骤可见 1.2 思路 一个线程顺序读取文件&#xff0c;达到设定的缓存块就发给另一个合成线程&#xff0c;主线程用来进行数据传递、显示进…

电子技术——A类输出阶

电子技术——A类输出阶 因为射极跟随器具有较低的输出阻抗&#xff0c;射极跟随器是A类输出阶的典型代表。我们之前已经学习过射极跟随器的小信号模型&#xff0c;本节我们讨论其大信号模型。 传输特性 下图展示了一个射极跟随器的原理图&#xff1a; 其中 Q1Q_1Q1​ 为射极…

并发编程-学习总结(下)

目录 1、Future 1.1、Callable和Runnable的不同 1.2、Future的主要功能 1.3、常用方法 1.4、Future使用注意事项 1.5、CompletableFuture(旅游平台问题) 1.5.1、需求 1.5.2、解决方案1&#xff1a;串行 1.5.3、解决方案2&#xff1a;线程池 1.5.4、解决方案3&#xf…

Prometheus本地存储和VictoriaMetrics远端存储

文章目录Prometheus本地存储简介blockWAL本地存储配置参数VictoriaMetrics简介单机版部署使用安装VictoriaMetrics配置Prometheus使用Victoriametrics配置Grafana以Victoriametrics作为数据源集群版部署使用部署vmstorage部署vmselect部署vminsert配置Prometheus使用vminsert配…

LearnOpenGL-入门-你好,三角形

本人刚学OpenGL不久且自学&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/LearnOpenGLProject LearnOpenGL中文官网&#xff1a;https://learnopengl-cn.github.io/ 文章目录图形渲染管线基本介绍着色器…

文献计量三大定律之一---洛特卡定律及普赖斯定律

科学生产率是洛特卡定律的基础&#xff0c;科学生产率”(Scientific Productivity)&#xff09;是指科学家&#xff08;科研人员&#xff09;在科学上所表现出的能力和工作效率&#xff0c;通常用其生产的科学文献的数量来衡量。 1926年&#xff0c;洛特卡在一篇论文中提出了科…

Windows作为操作系统的典型特征和主要功能

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Windows这个我们熟悉的不能再熟悉的系统。我们每天都在用Windows操作系统&#xff0c;但是其实我们每天直接在打交道的并不是Windows操作系统的内核&#xff0c;而是Windows操作系统的…

Docker部署Springboot项目(含MySQL+Redis)

使用Docker部署之前写的一个博客项目&#xff0c;主要用到了MySQL和Redis&#xff0c;Redis作网站访问量统计。下面会对具体的部署方式作详细讲解 一、服务器安装Docker 1、删除docker旧版本 sudo yum remove docker \docker-client \docker-client-latest \docker-common \…

(三十五)大白话MySQL一个事务多次查询一条数据读到的都是不同的值,这就是不可重复读?

上一讲我们说完了多个事务并发执行时候&#xff0c;对MySQL的缓存页里的同一行数据同时进行更新或者查询的时候&#xff0c;可能发生的脏写和脏读的问题 我们也都理解了&#xff0c;之所以会发生脏写和脏读&#xff0c;最关键的&#xff0c;其实是因为你一个事务写或者查的是人…

黑盒测试的常用方法

这里我们先设置一个示例,后面的文章中会根据示例来进行讲解 假设有一个程序是判断一个整形数字是否属于1-100 目录 1.等价类法 2.边界值法 3.判定表法 4.场景设计法 5.错误猜测法 6.正交法 1.等价类法 概念:系统性的确定要输入的测试条件的方法可以看出概念非常抽象,那…

命令执行漏洞 | iwebsec

文章目录1 靶场环境2 命令执行漏洞介绍3 靶场练习01-命令执行漏洞02-命令执行漏洞空格绕过03-命令执行漏洞关键命令绕过04-命令执行漏洞通配符绕过05-命令执行漏洞base64编码绕过4 命令执行漏洞危害01-读写系统文件02-执行系统命令03-种植恶意木马04-反弹shellpython反弹shellp…

Android 基础知识4-3.4 ImageView(图像视图)详解

一、ImageView简介 ImageView是Android开发中最常用的组件之一&#xff0c;主要用于显示图片&#xff0c;但是它不只是能显示图片&#xff0c;任何Drawable对象都可以使用它来显示。 二、ImageView 的继承关系 ImageView的继承关系 如下&#xff1a; java.lang.Object 《-- …