多线程应用

news2024/11/14 1:36:47

并发与并行

计算机操作系统对于并发性和并行性的概念给出的定义是:

并行性是指两个或多个事件在同一时刻发生; 并发性是指两个或多个事件在同一时间段内发生。

并发是指多个任务(线程)都请求运行,如果系统只有一个CPU,CPU只能按受一个任务,它把CPU运行时间划分成若干个时间段,再将时间段分配给各隔较短,使人感觉多个任务都在运行。

个线程安排轮流进行,在一个时间段的线程代码运行时,其它线程处于挂起状态。由于时间间 并行就是多个任务(线程)同时运行,就是甲任务进行的同时,乙任务也在进行,丙、丁任务等都在进行,当系统有一个以上CPU时,当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,多个线程互不抢占CPU资源,可以同时进行。

多进程并发与多线程并发

关于多进程和多线程,经常提到的就是进程所需的开销大,线程的开销小,线程比进程有优势。 但在真实的场景中使用并发时对多进程和多线程的选择没有这么简单。需要综合考虑各种因素。特别是对于进程池和线程池来说,进程和线程的数量很少,系统开销对比往往不是选择的主要因素。

对比项

多进程

多线程

结论

数据共享与同步

数据共享复杂,需要用IPC;数据是分开的,同步简单

多线程共享进程数据,数据共享简单;但也是因为这个原因导致同步复杂

各有优劣

硬件资源

进程需要的系统资源更多,但执行大任务时效率更高

线程时“轻量级进程”,系统开销小,执行小任务更有优势

线程占优

创建销毁及切换

开销大

开销远小于进程

线程占优

编程调试

简单

复杂

进程占优

可靠性

进程间互不影响

一个线程挂掉将导致整个进程挂掉

进程占优

可扩展性

适用于多核、多机部署

单机部署

进程占优

多线程应用

多线程应用目的:

为了划分关注点而使用:当编写一些需要同时操作的功能时,通过线程进行功能分离从而降低难度,提高响应速度。这种做法一般都是基于设计理念,而不是为了增加系统吞吐量,线程的数量和cpu硬件线程数无关。多见于用户界面。

为了性能:为了充分利用多核cpu,将任务分解为多个部分各自并行运行从而降低总的运行时间;或者增加单位时间内系统的吞吐量。

 注意:线程自身也是消耗资源的,windows上典型的线程栈带下为1M,linux一般达到10M;而且运行越多的线程,操作系统就需要做越多的上下文切换,将过多的时间消耗在上下文切换上,反而会导致程序下降。所以如果试图优化系统性能,必须根据cpu硬件线程数调整线程数量。

使用多线程优化系统性能和其它的性能优化策略一样,容易是代码复杂化,更难于开发和维护,只有当性能成为瓶颈的时候才值得去做。

多线程安全

编写多线程程序首先要考虑的就是把程序写正确,正因为线程间共享数据的简单和直接,导致非常容易使用错误的方法共享数据。写一个线程安全的程序远比写一个线程安全的类要复杂的多。线程安全的类只需使用同步原语保护好类内部状态即可。但C++对象的构造和析构无法通过对象自身的mutex进行保护。

同步原语

一般使用mutex和condition_variable就足以满足要求。

特殊情况考虑使用读写锁以提高效率。

不要使用递归锁,递归锁可能导致多层锁操作之间引发问题(例如内层锁修改了外层锁访问的变量,导致外层出现问题),而且mutex死锁更容易调试,通过定义NoLock版函数避免递归锁的使用。

使用atomic变量而不要简单的用volatile。

不用使用sleep进行线程同步操作,sleep不是线程同步原语。需要延时操作的地方使用互斥量或者条件变量的time_wait替代。

对象构造和析构

对象构造:

不要在对象构造函数中注册毁掉函数

不要在对象构造函数中将this指针传递给跨线程对象 因为此时对象还未初始化成功,别的线程可能访问这个对象,造成不可预料的后果。

对象析构: 当一个对象跨越多个线程被访问时,如果对象析构了,而另一个线程正在访问对象的成员函数,程序就会崩溃,而这是对象内部mutex无法解决的问题。

void TestClass::DoSomething()
{
    boost::unique_lock<boost::mutex> lock(m_mutex);
    //调用资源
}

~TestClass()
{
    boost::unique_lock<boost::mutex> lock(m_mutex);
    //释放资源
}

c++11之后的标准和boost都提供了智能指针解决了这个问题,所以在多线程程序中,使用智能指针不仅是一个好习惯,更是保证程序线程安全的必须。

内存模型

内存模型可分为静态内存模型和动态内存模型,静态内存模型主要涉及类的对象在内存中是如何存放的,即从结构(structural)方面来看一个对象在内存中的布局。 动态内存模型可理解为存储一致性模型,主要是从行为(behavioral)方面来看多个线程对同一个对象同时(读写)操作时(concurrency)所做的约束,动态内存模型理解起来稍微复杂一些,涉及了内存,Cache,CPU 各个层次的交互,尤其是在共享存储系统中,为了保证程序执行的正确性,就需要对访存事件施加严格的限制。

线程默认的内存序是 std::memory_order_seq_cst 以一个具体的例子来说明顺序一致性: 假设存在两个共享变量a,b,初始值均为0,两个线程运行不同的指令,如下表格所示,线程 1 设置 a 的值为 1,然后设置 R1 的值为 b,线程 2 设置 b 的值为 2,并设置 R2 的值为 a,请问在不加任何锁或者其他同步措施的情况下,R1,R2 的最终结果会是多少?

线程 1

线程 2

a = 1;

b = 2;

R1 = b;

R2 = a;

由于没有施加任何同步限制,两个线程将会交织执行,但交织执行时指令不发生重排,即线程 1 中的 a = 1 始终在 R1 = b 之前执行,而线程 2 中的 b = 2 始终在 R2 = a 之前执行 ,因此可能的执行序列共有 4!/(2!*2!) = 6 种 R1,R2 最终结果只有 3 种情况,分别是 R1 == 0, R2 == 1,R1 == 2, R2 == 0 和 R1 == 2, R2 == 1

但是编译器编译时会将指令重排以进行优化,可能出现R1 == 0, R2 == 0的结果。 另外,现代的 CPU 大都支持多发射和乱序执行,在乱序执行时,指令被执行的逻辑可能和程序汇编指令的逻辑不一致,在单线程条件下,CPU 的乱序执行不会带来大问题,但是在多核多线程时代,当多线程共享某一变量时,不同线程对共享变量的读写就应该格外小心,不适当的乱序执行可能导致程序运行错误。因此,CPU 的乱序执行也需要作出适当的约束。

经典的double check问题:

boost::shared_ptr<Resource> pResource;
boost::mutex resourceMutex;
void foo()
{
    if (!pResource)
    {
         boost::unique_lock<boost::mutex> lock(resourceMutex);
         if (!pResource)
         {
            pResource.reset(new Resource);
         }
    }
    pResource->DoSomething();
}

我们将初始化过程分解就可以发现原因:

pResource.reset(new Resource);
//等效
Resource *pTemp = ::operator new(sizeof(Resource));
pTemp->Resource();
pResource.reset(pTemp);
}

实际执行的时候,第三步返回指针可能会在构造函数执行之前就完成了,所以通过第一次if (!pResource)判断的线程可能在一个未初始化构造的对象上执行了操作。

volatile关键字

c++中的volatile关键字主要作用:

  • 易变性,保证变量的每次读写都重新从内存读,而不会使用寄存器的值。保证下面循环可以退出

volatile bool flag = false;
Thread1()
{
    flag = true;
}

Thread2()
{
    while(!flag)
    {
        //线程循环
    }
}

  • 不可优化

void DoSomething()
{
    volatile int a;
    a = 1;
    a = 2;
    a = 3;
    //其他操作
}

编译器优化时会直接优化成一句a=3,加了volatile关键字就不会进行这些优化

  • volatile没有支持内存模型顺序性的功能

volatile bool flag = false;
int somevar = 0
Thread1()
{
    somevar = 1
    flag = true;
}

Thread2()
{
    while (!flag)
    {
        //线程循环
    }
    assert(somevar == 1);
}

线程2的断言无法保证一定成立,因为somevar=1与flag = true的执行顺序是不确定的。

volatile在c++中不是线程同步的手段,多线程访问共享变量使用原子操作。

多线程模型

每个请求/事件创建一个单独的线程进行处理。创建销毁线程开销大,不能满足处理大量请求的场景。

while (true)
{
    boost::function<void ()> task;
    if (BlockingQueue.get(task))
    {
        boost::thread newThread(task);
        newThread.detach();
    }
}

需要考虑线程返回值或者同步需求的情况,可以使用future模式。

使用线程池

一些特殊的线程例如写程序日志等

生产者消费者线程池

或者叫做半同步半异步线程池,主线程处理I/O事件并解析然后再往队列丢数据,然后消费者读出数据进行应用逻辑处理;优点:简化编程将低层的异步I/O和高层同步应用服务分离,且没有降低低层服务性能。集中层间通信。

缺点:需要线程间传输数据,因此而带来的动态内存分配,数据拷贝,语境切换带来开销。高层服务不可能从底层异步服务效率中获益。

生产者消费者模式监听线程和工作线程间通过一个消息队列来交换数据。这会带来数据传递开销,。同时,监听线程和工作线程都需要去访问消息队列,造成 了资源的竞争,需要额外的同步机制来协调他们的行为,包括监听线程获取和释放资源锁,对应的工作线程获取和释放资源锁,以及监听线程在将一个请求放入队列 后通知工作线程带来的开销,我们称此为同步开销,HS/HA模式的同步开销大于L/F的同步开销,。一个请求由监听线程负责放入消息队列,但是却由工作线 程来处理,所以,每个请求都会造成一次线程上下文切  换,由此带来的开销我们称为上下文开销。

 T (H/H)=T(多路分离)+T(分配)+T(处理)+T(同步)+T(数据传递)+T(上下文)

leader/follower线程池

在LF线程池中,线程可处在3种线程状态之一: leader、follower或processor。处于leader状态的线程负责监听网络端口,当有消息到达时,该线程负责消息分离,并从处于 follower状态中的线程中按照某种机制如FIFO或基于优先级等选出一个来当新的leader,然后将自己设置为processor状态去分配和处 理该事件。处理完毕后线程将自身的状态设置为follower状态去等待重新成为leader。在整个线程池中同一时刻只有一个线程可以处于leader 状态,这保证了同一事件不会被多个线程重复处理。  缺点:实现复杂性和缺乏灵活性;  优点:增强了CPU高速缓存相似性,消除了动态内存分配和线程间的数据交换。

L/F模式处理一个消息的时间为多路分离、分配、处理的时间,加上线程管理时间,LF中多个线程共享一个事件源,所以,需要协调它们间的行为,即 有同步开销,L/F同步开销仅为申请/释放锁的开销,在LF处理请求过程中并不需要线程上下文切换,但是在线程由follower成为leader时需要 进行线程上下文切换,所以当两个请求同时到达时,这种上下文切换会影响第二个请求的处理时间,也会带来一定的上下文开销。  T(L/F)=T(多路分离)+T(分配)+T(处理)+T(同步)+T(上下文)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1939711.html

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

相关文章

java学习--断点调试

可进入调用的方法里看源码

C2W2.Assignment.Parts-of-Speech Tagging (POS).Part1

理论课&#xff1a;C2W2.Part-of-Speech (POS) Tagging and Hidden Markov Models 文章目录 0 Data Sources1 POS Tagging1.1 TrainingTransition countsEmission countsTag countsExercise 01 1.2 TestingExercise 02 理论课&#xff1a; C2W2.Part-of-Speech (POS) Tagging…

创建自己的 app: html网页直接打包成app;在线网页打包app工具fusionapp、pake

1、html网页直接打包成app 主要通过hbuilderx框架工具来进行打包 https://www.dcloud.io/hbuilderx.html 参考&#xff1a; https://www.bilibili.com/video/BV1XG411r7QZ/ https://www.bilibili.com/video/BV1ZJ411W7Na 1&#xff09;网页制作 这里做的工具是TodoList 页面&a…

数据结构——栈的实现(java实现)与相应的oj题

文章目录 一 栈栈的概念:栈的实现&#xff1a;栈的数组实现默认构造方法压栈获取栈元素的个数出栈获取栈顶元素判断当前栈是否为空 java提供的Stack类Stack实现的接口&#xff1a; LinkedList也可以当Stack使用虚拟机栈&#xff0c;栈帧&#xff0c;栈的三个概念 二 栈的一些算…

Android 11 HAL层集成FFMPEG

1.集成目录&#xff1a; android/vendor/noch/common/external/NoboMediaCodec 2.文件夹目录 3. Android.mk实现 # Copyright #LOCAL_PATH : $(call my-dir)SF_COMMON_MK : $(LOCAL_PATH)/common.mkinclude $(call first-makefiles-under,$(LOCAL_PATH))4.common.mk实现 # #…

Xilinx FPGA DDR4 接口配置基础(PG150)

1. 简介 1.1 DDR4 SDRAM 控制器主要特点 支持8到80位接口宽度的组件&#xff08;支持 RDIMM、LRDIMM、UDIMM 和 SODIMM&#xff09; 最大组件限制为9&#xff0c;此限制仅适用于组件&#xff0c;不适用于 DIMM。密度支持 最高支持 32 GB 的组件密度&#xff0c;64 GB 的 LRDI…

初识godot游戏引擎并安装

简介 Godot是一款自由开源、由社区驱动的2D和3D游戏引擎。游戏开发虽复杂&#xff0c;却蕴含一定的通用规律&#xff0c;正是为了简化这些通用化的工作&#xff0c;游戏引擎应运而生。Godot引擎作为一款功能丰富的跨平台游戏引擎&#xff0c;通过统一的界面支持创建2D和3D游戏。…

数字集成电路(3)

光刻&#xff08;photolithography&#xff09; 工艺步骤&#xff1a; 扩散和离子注入&#xff1a;900~1100℃ 淀积 刻蚀 平面化 衬底选择&#xff1a;常用&#xff08;100&#xff09;晶面&#xff08;原因&#xff1a;面密度小&#xff0c;界面态少&#xff09; 设计规…

【vue教程】四. Vue 计算属性和侦听器

目录 本章涵盖知识点回顾计算属性&#xff08;Computed&#xff09;创建计算属性计算属性的多样性计算属性的数组过滤计算属性的复杂表达式 计算属性 vs 方法计算属性的实例演示 侦听器&#xff08;Watchers&#xff09;创建侦听器侦听器的高级用法侦听器的深度观察侦听器的立即…

【ffmpeg命令基础】过滤处理

文章目录 前言过滤处理的介绍两种过滤类型简单滤波图简单滤波图是什么简单滤波示例 复杂滤波图复杂滤波是什么区别示例 总结 前言 FFmpeg是一款功能强大的开源音视频处理工具&#xff0c;广泛应用于音视频的采集、编解码、转码、流化、过滤和播放等领域。1本文将重点介绍FFmpe…

mysql存储引擎和备份

索引 事务 存储引擎 概念&#xff1a;存储引擎&#xff0c;就是一种数据库存储数据的机制&#xff0c;索引的技巧&#xff0c;锁定水平。 存储引擎。存储的方式和存储的格式。 存储引擎也属于mysql当中的组件&#xff0c;实际上操作的&#xff0c;执行的就是数据的读写I/O。…

ROC曲线和AUC

ROC曲线能更稳定反映模型的性能&#xff0c;对测试集合中数据分布的变化不敏感 AUC&#xff1a;当随机挑选一个正样本和一个负样本&#xff0c;根据当前的分类器计算得到的score将这个正样本排在负样本前面的概率 从AUC判断分类器&#xff08;预测模型&#xff09;优劣的标准&a…

【QT开发(19)】2023-QT 5.14.2实现Android开发,使用新版SDK,试图支持 emulator -avd 虚拟机

之前的博客【QT开发&#xff08;17&#xff09;】2023-QT 5.14.2实现Android开发&#xff0c;SDK是24.x版本的&#xff0c;虚拟机是32位的&#xff0c;但是现在虚拟机是64位的了&#xff0c;需要升级SDK匹配虚拟机 文章目录 最后的效果1.1 下载最新版 SDK tools (仅限命令行工…

JavaWeb-【3】DOM

笔记系列持续更新&#xff0c;真正做到详细&#xff01;&#xff01;本次系列重点讲解后端&#xff0c;那么第一阶段先讲解前端【续上篇CSS和JavaScript】 目录 1、dom介绍 2、html-dom 3、document 4、应用实例 ①、应用实例1 ②、多选框案例 ③、图片切换案例 ④、添…

高性能图数据库Neo4j从入门到实战

图数据库Neo4j介绍 什么是图数据库&#xff08;graph database&#xff09; 随着社交、电商、金融、零售、物联网等行业的快速发展&#xff0c;现实社会织起了了一张庞大而复杂的关系网&#xff0c;传统数据库很难处理关系运算。大数据行业需要处理的数据之间的关系随数据量呈…

密码学基础-Hash、MAC、HMAC 的区别与联系

密码学基础-Hash、MAC、HMAC 的区别与联系 Hash Hash 是一种从一段数据中创建小的数字“指纹”的方法。就像一个人的指纹代表一个人的信息一样&#xff0c;Hash 对输入的数据进行整理&#xff0c;生成一个代表该输入数据的“指纹” 数据。通常该指纹数据也可称之为摘要、散列…

CefSharp音视频编译与免费下载

注&#xff1a;Cefharp 音频和视频播放编译&#xff0c;生成相应的dll文件&#xff0c;从而支持项目开发。 建议编译至少 16G 的 RAM和至少 250G 的 SSD。该脚本以 E 盘为例&#xff0c;您需要在 E 盘上手动创建 cef 文件夹。禁止在转载后通过发布其他平台向用户收取下载费用。…

全国区块链职业技能大赛第八套区块链产品需求分析与方案设计

任务1-1:区块链产品需求分析与方案设计 医疗健康平台中涉及到医院、医生、患者等参与方,他们需要在区块链医疗健康平台中完成账户注册、身份上链、挂号就诊、查询病例等多种业务活动。通过对业务活动的功能分析,可以更好的服务系统的开发流程。基于医疗健康平台系统架构,以…

【数据结构进阶】二叉搜索树

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; C || 数据结构 目录 &#x1f308;前言&#x1f525;二叉搜索树&#x1f525; 二叉搜索树的实现Insert&#xff08;插入&#xff09;find&#xff08;查找&#xff09;erase(删除)destro…

毕业/期刊论文发表必备:YOLOv5 / v7 / v8 /v10算法网络结构图【文末提供原型文件下载地址】

前言:Hello大家好,我是小哥谈。同学们在写YOLO算法相关毕业论文/期刊论文的时候,不可避免的会用到相关版本的网络结构图,曾有很多小伙伴私信我索要原型文件,本文就给大家提供YOLOv5/v7/v8/v10版本算法网络结构图及原型文件下载地址。🌈 目录 🚀1.网络结构图 �…