并发 订阅
并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。 展开全文
并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
信息
外文名
Concurrent
拼    音
bìng fā
中文名
并发
并发词语概念
词目:并发基本解释:[] 指由己患的疾病引起[另一种病]。并发症。 [1]  引证解释:1. 同时开放。晋 傅玄 《傅子》卷三:“晔若春华之并发,馥若秋兰之俱茂。”2. 谓跟着发生。 [1] 
收起全文
精华内容
下载资源
问答
  • 并发
    千次阅读
    2021-11-28 19:06:48

    高并发,几乎是每个程序员都想拥有的经验。原因很简单:随着流量变大,会遇到各种各样的技术问题,比如接口响应超时、CPU load升高、GC频繁、死锁、大数据量存储等等,这些问题能推动我们在技术深度上不断精进。

    在过往的面试中,如果候选人做过高并发的项目,我通常会让对方谈谈对于高并发的理解,但是能系统性地回答好此问题的人并不多,大概分成这样几类:

    1、对数据化的指标没有概念:不清楚选择什么样的指标来衡量高并发系统?分不清并发量和QPS,甚至不知道自己系统的总用户量、活跃用户量,平峰和高峰时的QPS和TPS等关键数据。

    2、设计了一些方案,但是细节掌握不透彻:讲不出该方案要关注的技术点和可能带来的副作用。比如读性能有瓶颈会引入缓存,但是忽视了缓存命中率、热点key、数据一致性等问题。

    3、理解片面,把高并发设计等同于性能优化:大谈并发编程、多级缓存、异步化、水平扩容,却忽视高可用设计、服务治理和运维保障。

    4、掌握大方案,却忽视最基本的东西:能讲清楚垂直分层、水平分区、缓存等大思路,却没意识去分析数据结构是否合理,算法是否高效,没想过从最根本的IO和计算两个维度去做细节优化。

    这篇文章,我想结合自己的高并发项目经验,系统性地总结下高并发需要掌握的知识和实践思路,希望对你有所帮助。内容分成以下3个部分:

    • 如何理解高并发?

    • 高并发系统设计的目标是什么?

    • 高并发的实践方案有哪些?

    01 如何理解高并发?

    高并发意味着大流量,需要运用技术手段抵抗流量的冲击,这些手段好比操作流量,能让流量更平稳地被系统所处理,带给用户更好的体验。

    我们常见的高并发场景有:淘宝的双11、春运时的抢票、微博大V的热点新闻等。除了这些典型事情,每秒几十万请求的秒杀系统、每天千万级的订单系统、每天亿级日活的信息流系统等,都可以归为高并发。

    很显然,上面谈到的高并发场景,并发量各不相同,那到底多大并发才算高并发呢?

    1、不能只看数字,要看具体的业务场景。不能说10W QPS的秒杀是高并发,而1W QPS的信息流就不是高并发。信息流场景涉及复杂的推荐模型和各种人工策略,它的业务逻辑可能比秒杀场景复杂10倍不止。因此,不在同一个维度,没有任何比较意义。

    2、业务都是从0到1做起来的,并发量和QPS只是参考指标,最重要的是:在业务量逐渐变成原来的10倍、100倍的过程中,你是否用到了高并发的处理方法去演进你的系统,从架构设计、编码实现、甚至产品方案等维度去预防和解决高并发引起的问题?而不是一味地升级硬件、加机器做水平扩展。

    此外,各个高并发场景的业务特点完全不同:有读多写少的信息流场景、有读多写多的交易场景,那是否有通用的技术方案解决不同场景的高并发问题呢

    我觉得大的思路可以借鉴,别人的方案也可以参考,但是真正落地过程中,细节上还会有无数的坑。另外,由于软硬件环境、技术栈、以及产品逻辑都没法做到完全一致,这些都会导致同样的业务场景,就算用相同的技术方案也会面临不同的问题,这些坑还得一个个趟。

    因此,这篇文章我会将重点放在基础知识、通用思路、和我曾经实践过的有效经验上,希望让你对高并发有更深的理解。

    02 高并发系统设计的目标是什么?

    先搞清楚高并发系统设计的目标,在此基础上再讨论设计方案和实践经验才有意义和针对性。

    2.1 宏观目标

    高并发绝不意味着只追求高性能,这是很多人片面的理解。从宏观角度看,高并发系统设计的目标有三个:高性能、高可用,以及高可扩展。

    1、高性能:性能体现了系统的并行处理能力,在有限的硬件投入下,提高性能意味着节省成本。同时,性能也反映了用户体验,响应时间分别是100毫秒和1秒,给用户的感受是完全不同的。

    2、高可用:表示系统可以正常服务的时间。一个全年不停机、无故障;另一个隔三差五出线上事故、宕机,用户肯定选择前者。另外,如果系统只能做到90%可用,也会大大拖累业务。

    3、高扩展:表示系统的扩展能力,流量高峰时能否在短时间内完成扩容,更平稳地承接峰值流量,比如双11活动、明星离婚等热点事件。

    这3个目标是需要通盘考虑的,因为它们互相关联、甚至也会相互影响。

    比如说:考虑系统的扩展能力,你会将服务设计成无状态的,这种集群设计保证了高扩展性,其实也间接提升了系统的性能和可用性。

    再比如说:为了保证可用性,通常会对服务接口进行超时设置,以防大量线程阻塞在慢请求上造成系统雪崩,那超时时间设置成多少合理呢?一般,我们会参考依赖服务的性能表现进行设置。

    2.2 微观目标

    再从微观角度来看,高性能、高可用和高扩展又有哪些具体的指标来衡量?为什么会选择这些指标呢?

    ❇ 性能指标

    通过性能指标可以度量目前存在的性能问题,同时作为性能优化的评估依据。一般来说,会采用一段时间内的接口响应时间作为指标。

    1、平均响应时间:最常用,但是缺陷很明显,对于慢请求不敏感。比如1万次请求,其中9900次是1ms,100次是100ms,则平均响应时间为1.99ms,虽然平均耗时仅增加了0.99ms,但是1%请求的响应时间已经增加了100倍。

    2、TP90、TP99等分位值:将响应时间按照从小到大排序,TP90表示排在第90分位的响应时间, 分位值越大,对慢请求越敏感。

    3、吞吐量:和响应时间呈反比,比如响应时间是1ms,则吞吐量为每秒1000次。

    通常,设定性能目标时会兼顾吞吐量和响应时间,比如这样表述:在每秒1万次请求下,AVG控制在50ms以下,TP99控制在100ms以下。对于高并发系统,AVG和TP分位值必须同时要考虑。

    另外,从用户体验角度来看,200毫秒被认为是第一个分界点,用户感觉不到延迟,1秒是第二个分界点,用户能感受到延迟,但是可以接受。

    因此,对于一个健康的高并发系统,TP99应该控制在200毫秒以内,TP999或者TP9999应该控制在1秒以内。

     

    ❇ 可用性指标

    高可用性是指系统具有较高的无故障运行能力,可用性 = 正常运行时间 / 系统总运行时间,一般使用几个9来描述系统的可用性。

    对于高并发系统来说,最基本的要求是:保证3个9或者4个9。原因很简单,如果你只能做到2个9,意味着有1%的故障时间,像一些大公司每年动辄千亿以上的GMV或者收入,1%就是10亿级别的业务影响。

    ❇ 可扩展性指标

    面对突发流量,不可能临时改造架构,最快的方式就是增加机器来线性提高系统的处理能力。

    对于业务集群或者基础组件来说,扩展性 = 性能提升比例 / 机器增加比例,理想的扩展能力是:资源增加几倍,性能提升几倍。通常来说,扩展能力要维持在70%以上。

    但是从高并发系统的整体架构角度来看,扩展的目标不仅仅是把服务设计成无状态就行了,因为当流量增加10倍,业务服务可以快速扩容10倍,但是数据库可能就成为了新的瓶颈。

    像MySQL这种有状态的存储服务通常是扩展的技术难点,如果架构上没提前做好规划(垂直和水平拆分),就会涉及到大量数据的迁移。

    因此,高扩展性需要考虑:服务集群、数据库、缓存和消息队列等中间件、负载均衡、带宽、依赖的第三方等,当并发达到某一个量级后,上述每个因素都可能成为扩展的瓶颈点。

    03 高并发的实践方案有哪些?

    了解了高并发设计的3大目标后,再系统性总结下高并发的设计方案,会从以下两部分展开:先总结下通用的设计方法,然后再围绕高性能、高可用、高扩展分别给出具体的实践方案。

    3.1 通用的设计方法

    通用的设计方法主要是从「纵向」和「横向」两个维度出发,俗称高并发处理的两板斧:纵向扩展和横向扩展。

    ❇ 纵向扩展(scale-up)

    它的目标是提升单机的处理能力,方案又包括:

    1、提升单机的硬件性能:通过增加内存、CPU核数、存储容量、或者将磁盘升级成SSD等堆硬件的方式来提升。

    2、提升单机的软件性能:使用缓存减少IO次数,使用并发或者异步的方式增加吞吐量。

    ❇ 横向扩展(scale-out)

    因为单机性能总会存在极限,所以最终还需要引入横向扩展,通过集群部署以进一步提高并发处理能力,又包括以下2个方向:

    1、做好分层架构:这是横向扩展的提前,因为高并发系统往往业务复杂,通过分层处理可以简化复杂问题,更容易做到横向扩展。

    上面这种图是互联网最常见的分层架构,当然真实的高并发系统架构会在此基础上进一步完善。比如会做动静分离并引入CDN,反向代理层可以是LVS+Nginx,Web层可以是统一的API网关,业务服务层可进一步按垂直业务做微服务化,存储层可以是各种异构数据库。

    2、各层进行水平扩展:无状态水平扩容,有状态做分片路由。业务集群通常能设计成无状态的,而数据库和缓存往往是有状态的,因此需要设计分区键做好存储分片,当然也可以通过主从同步、读写分离的方案提升读性能。

    3.2 具体的实践方案

    下面再结合我的个人经验,针对高性能、高可用、高扩展3个方面,总结下可落地的实践方案。

    ❇ 高性能的实践方案

    1、集群部署,通过负载均衡减轻单机压力。

    2、多级缓存,包括静态数据使用CDN、本地缓存、分布式缓存等,以及对缓存场景中的热点key、缓存穿透、缓存并发、数据一致性等问题的处理。

    3、分库分表和索引优化,以及借助搜索引擎解决复杂查询问题。

    4、考虑NoSQL数据库的使用,比如HBase、TiDB等,但是团队必须熟悉这些组件,且有较强的运维能力。

    5、异步化,将次要流程通过多线程、MQ、甚至延时任务进行异步处理。

    6、限流,需要先考虑业务是否允许限流(比如秒杀场景是允许的),包括前端限流、Nginx接入层的限流、服务端的限流。

    7、对流量进行削峰填谷,通过MQ承接流量。

    8、并发处理,通过多线程将串行逻辑并行化。

    9、预计算,比如抢红包场景,可以提前计算好红包金额缓存起来,发红包时直接使用即可。

    10、缓存预热,通过异步任务提前预热数据到本地缓存或者分布式缓存中。

    11、减少IO次数,比如数据库和缓存的批量读写、RPC的批量接口支持、或者通过冗余数据的方式干掉RPC调用。

    12、减少IO时的数据包大小,包括采用轻量级的通信协议、合适的数据结构、去掉接口中的多余字段、减少缓存key的大小、压缩缓存value等。

    13、程序逻辑优化,比如将大概率阻断执行流程的判断逻辑前置、For循环的计算逻辑优化,或者采用更高效的算法。

    14、各种池化技术的使用和池大小的设置,包括HTTP请求池、线程池(考虑CPU密集型还是IO密集型设置核心参数)、数据库和Redis连接池等。

    15、JVM优化,包括新生代和老年代的大小、GC算法的选择等,尽可能减少GC频率和耗时。

    16、锁选择,读多写少的场景用乐观锁,或者考虑通过分段锁的方式减少锁冲突。

    上述方案无外乎从计算和 IO 两个维度考虑所有可能的优化点,需要有配套的监控系统实时了解当前的性能表现,并支撑你进行性能瓶颈分析,然后再遵循二八原则,抓主要矛盾进行优化。

    ❇ 高可用的实践方案

    1、对等节点的故障转移,Nginx和服务治理框架均支持一个节点失败后访问另一个节点。

    2、非对等节点的故障转移,通过心跳检测并实施主备切换(比如redis的哨兵模式或者集群模式、MySQL的主从切换等)。

    3、接口层面的超时设置、重试策略和幂等设计。

    4、降级处理:保证核心服务,牺牲非核心服务,必要时进行熔断;或者核心链路出问题时,有备选链路。

    5、限流处理:对超过系统处理能力的请求直接拒绝或者返回错误码。

    6、MQ场景的消息可靠性保证,包括producer端的重试机制、broker侧的持久化、consumer端的ack机制等。

    7、灰度发布,能支持按机器维度进行小流量部署,观察系统日志和业务指标,等运行平稳后再推全量。

    8、监控报警:全方位的监控体系,包括最基础的CPU、内存、磁盘、网络的监控,以及Web服务器、JVM、数据库、各类中间件的监控和业务指标的监控。

    9、灾备演练:类似当前的“混沌工程”,对系统进行一些破坏性手段,观察局部故障是否会引起可用性问题。

    高可用的方案主要从冗余、取舍、系统运维3个方向考虑,同时需要有配套的值班机制和故障处理流程,当出现线上问题时,可及时跟进处理。

    ❇ 高扩展的实践方案

    1、合理的分层架构:比如上面谈到的互联网最常见的分层架构,另外还能进一步按照数据访问层、业务逻辑层对微服务做更细粒度的分层(但是需要评估性能,会存在网络多一跳的情况)。

    2、存储层的拆分:按照业务维度做垂直拆分、按照数据特征维度进一步做水平拆分(分库分表)。

    3、业务层的拆分:最常见的是按照业务维度拆(比如电商场景的商品服务、订单服务等),也可以按照核心接口和非核心接口拆,还可以按照请求源拆(比如To C和To B,APP和H5)。

    最后的话

    高并发确实是一个复杂且系统性的问题,由于篇幅有限,诸如分布式Trace、全链路压测、柔性事务都是要考虑的技术点。另外,如果业务场景不同,高并发的落地方案也会存在差异,但是总体的设计思路和可借鉴的方案基本类似。

    高并发设计同样要秉承架构设计的3个原则:简单、合适和演进。“过早的优化是万恶之源”,不能脱离业务的实际情况,更不要过度设计,合适的方案就是最完美的。

    更多相关内容
  • 这就是最正宗的《Java 并发编程实战》带目录 用福昕阅读器打开查看特别的清晰
  • C++ Concurrency in Action, 2nd edition(C++并发编程第二版),非第一版中文版那几个不懂C++的作者翻译版本(瞎搞),这个版本翻译的还可以~
  • C#调用httplistener实现简单的http服务器例子:编译后是一个控制台应用程序,启动后,可通过 http://127.0.0.1/ 访问,采用了回调模式提供http服务,支持高并发
  • 千万级高并发架构

    2018-04-13 16:47:01
    千万级高并发架构,互联网核心技术,含对分布式系统的详细技术描述
  • JAVA并发编程艺术 高清pdf : 1.并发变成的挑战 2. java并发机制的底层实现原理 3. java 内存模型 4. java并发编程基础 5.java中的锁。。。。。。。
  • 深入讲解java并发编程技术,多线程、锁以及java内存模型等
  • java多线程与高并发java多线程与高并发java多线程与高并发
  • java 并发编程的艺术pdf清晰完整版 源码
  • Windows下基于socket多线程并发通信的实现

    千次下载 热门讨论 2015-04-07 15:06:06
    本文介绍了在Windows 操作系统下基于TCP/IP 协议Socket 套接口的通信机制以及多线程编程知识与技巧,并给出多线程方式实现多用户与服务端(C/S)并发通信模型的详细算法,最后展现了用C++编写的多用户与服务器通信的...
  • Java架构师系列课程是针对有志向架构师发展的广大学员而设置,不管你是工作一到三年,还是三到五年,本课程都会对你产生巨大的提升,让你的薪资大幅翻倍,走向BAT架构师的伟大梦想。
  • java高并发秒杀api源码

    热门讨论 2016-11-29 23:01:35
    java高并发秒杀api源码
  • C++并发编程实战

    热门讨论 2015-11-02 21:06:44
    主要讲的是C++多线程的知识,并说了线程安全的问题,都是通过各种例子来说明的,简单明了。
  • 很好的一本并发编程书,自己做了完整的书签。方便阅读 第1章 并发的威力与风险 1 1.1 线程:程序的执行流程 1 1.2 并发的威力 1 1.3 并发的风险 4 1.4 小结 9 第2章 分工原则 11 2.1 从顺序到...
  • C#版支持高并发的HTTP服务器源码

    热门讨论 2016-09-30 11:44:04
    C#版支持高并发的HTTP服务器源码,异步处理并发调用,应用于WINFORM程序中,创建自己的HTTP SERVER的首选办法。
  • 并发计算中的串行思考

    千次阅读 2022-03-07 00:08:29
    软件系统性能的提升的重要方法之一是支持并发性编程,尤其是采用多核体系结构的时候。在全局数据库、云计算和区块链应用程序中,并发性对于实现容错和分布式服务也是至关重要的。然而,对并发性的掌握一...

    软件系统性能的提升的重要方法之一是支持并发性编程,尤其是采用多核体系结构的时候。在全局数据库、云计算和区块链应用程序中,并发性对于实现容错和分布式服务也是至关重要的。然而,对并发性的掌握一直是令人畏惧的挑战之一。并发编程是困难的,要同时处理许多可能任务的非确定性行为,包括故障、操作系统、共享内存架构和异步。

    5a25bcb88361551223b923dd129f934b.png

    并发执行与顺序执行

    理解并发计算的主要方法就是将并发域中的问题转换为顺序域中更简单的问题,这又是一种权衡,也是一个连接两个领域的桥梁。

    首先,可以并发访问的对象或服务,只有在进程依次访问对象的情况下,才会执行期望的行为。因此,串行计算可以用来指定共享对象,例如经典的数据结构(队列、堆栈和列表)、可读取或修改的寄存器或数据库事务。这使得理解正在实现的对象变得容易,而不像真正的并发计算那样困难或不自然。

    其次,串行计算为高效、可伸缩和容错的并发对象提供了实现的技术。锁是对共享数据和并发控制/服务协议的独占访问,复制数据的协议以相同的顺序在本地执行对象操作,可靠的通信协议如原子广播可以用于进程之间的通信,分布式数据结构,如区块链的提交协议可以确保原子性属性。常用的技术包括时间戳、投票共识、成员组关系和故障检测器,由进度条件来指定,以保证实际执行操作。

    桥接器在并发执行和串行执行之间建立连接。它强制执行安全属性,通过这些属性,并发执行看起来好像是在某些顺序交织中串行执行对象上的调用操作。一致性条件定义了对象操作的并发调用,然后可以根据其顺序规范进行测试。

    演变的历史是这样的,从互斥锁开始,然后在消息传递系统上实现读/写寄存器,最后是通过强大的同步机制实现任意对象,以及区块链的高度可扩展性和防篡改的方式。

    706cf577e3a6739281037f1003f6ec7c.png

    互斥锁

    并发的出现是为了有效地利用顺序执行的计算机,顺序执行的计算机一次只能执行一条指令,让用户认为他们的程序通过操作系统同时运行。

    一旦并发运行的程序开始相互交互,危机就会浮现,当时的并发编程没有任何概念基础,程序错误百出,还会并会导致程序行为的不一致。1965年,Dijkstra 发现部分代码的互斥锁是编程的一个基本概念,从而打开了并发编程的道路。

    互斥锁/代码算法包含了进程调用类似acquire ()和 release ()的代码,这些代码用于称为临界区的一段代码。通常执行它的环境是异步的,其中进程速度是任意的,彼此独立。互斥锁算法保证了两个性质。

    • 没有两个进程同时执行临界互斥锁。

    • 如果一个或多个进程调用并发执行的 acquire ()操作,则只有一个进程调用并执行临界区。

    锁并不能防止出现某些进程永远不能进入临界区的特定场景。

    互斥锁算法

    假设两个进程 p1和 p2共享三个读/写原子寄存器,F1、 F2和 L,最初的 F1和F2是关闭的,而 L不需要初始化。这两个进程都可以读取所有寄存器。原子寄存器味着寄存器上的读写操作是按顺序执行的。

    当进程 p1 调用 acquire ()时,它首先设置自己的标志,从而表明它在竞争,然后在 L中写入自己的名字,表明它是这个寄存器的最后一个写入者。接下来,进程p1重复读取所有寄存器,直到它看到 F1或F2的标志不是p1, 或者p1不再是 LAST 的最后一个写入者。当这种情况发生时,p1终止它的调用,操作 release ()。

    互斥锁是通过顺序思维掌握并发编程的第一种机制,导致了开始为并发计算提供了科学的基础概念,例如竞争条件和原子性的概念。使用锁控制对数据的访问(例如,两阶段锁) ,是并发控制的起源。

    1b570b1cee608c2d7a59afd4ff7b488c.png

    从资源到对象

    开始的时候,临界区是物理资源的封装使用,物理资源本身的性质是按顺序指定的(例如,磁盘、打印机、处理器),然后使用锁来保护对简单数据(如文件)的并发访问。然而,当临界区开始被用来封装更一般的共享对象,就需要新的处理方法了。

    数据不是物理资源,共享对象不同于物理对象。它不需要独占访问,一个进程可以读取一个文件的数据,而另一个进程可以并发地修改它。无需使用互斥锁即可实现纯数字对象的并发计算成为可能,操作可以在时间上重叠。

    此外,在存在异步和进程崩溃的情况下,互斥锁不能用于实现对象。如果一个进程在它的临界区内崩溃,那么其他进程无法判断它是崩溃了还是只是速度太慢,从而无法访问该对象。

    在发生并发访问以共享数据的情况下,需要一个一致性条件来定义哪些并发操作被认为是正确的。从外部观察者的角度来看,所有的操作都必须显示为顺序执行。这就是循序一致性/服务的概念 ,自1976年以来一直在数据库场景中使用,以保证事务看起来是自动执行的。但是,循序一致性/服务是不可组合的。线性化(或原子性)的强一致性条件要求操作的总顺序遵守非重叠操作的顺序。

    6aa87dc585207620c34b6bb0cc0462b8.png

    基于消息系统的读写寄存器

    最基本的共享对象就是读/写寄存器。在共享内存中,简单的寄存器支持只有一个进程可以写,另一个进程可以读,而多写多读(MWMR)寄存器则支持每个进程都可以写,每个进程都可以读。

    分布式消息系统通常支持共享内存的抽象,并得到了广泛接受,这种抽象提供了单处理器概念的自然转换,并简化了编程任务。在可靠的异步消息传递系统上构建原子读/写寄存器相对容易,但如果进程可能崩溃,则需要更复杂的算法:

    • 一种在 n 个异步消息进程系统上实现原子读/写寄存器的算法,其中最多小于n/2的进程可能崩溃。

    • 不可能在 n/2的进程崩溃时构建原子读/写寄存器。

    这样的算法说明了减少并发对顺序执行的重要性,其设计原则是每个写入的值都有一个标识,每个进程既是客户端又是服务器,构建的多写多读(MWMR)寄存器——R,任何进程都可以读写寄存器。在客户端,进程P可以调用操作 R.write (v)在 REG 中写一个值 v,R.read ()以获取其当前值。在服务器端,进程P管理两个本地变量: 本地实现 R-i和 Timestamp-i (包含由序列号和进程标识组成的时间戳)。时间戳构成了在 R-i 中保存值 v 的“标识”,也就是说,这个值在此时是由这个进程写入的,任何两个时间戳完全是按照它们的字典序排序的。

    进程 P向所有进程广播查询,并等待大多数进程的确认即投票仲裁,这就意味着读/写寄存器 R具有原子性属性。

    当流程P调用 R.write (v)时,它首先创建一个标记,该标记将标识由此写操作调用生成的查询/响应消息。然后,它执行查询/响应模式,了解在大多数进程的本地变量 Timestamp-j 中保存的最高序列号。完成后,进程P计算时间戳 ts,这个时间戳将与它要在 R中写入的值 v 相关联。最后,进程P启动第二个查询/响应模式,在该模式中将(v,ts)广播给所有进程。当它从投票仲裁者收到相关的确认时,才会终止这一操作。

    在服务器端,其他进程在写操作的第一阶段接收进程P发送的 WRITE R 消息,并发送回一个确认,该确认带有与它在 R-i 中保存的新值相关的序列号。当在写操作的第二阶段接收到由进程P发送的 WRITE R消息时,如果接收到的时间戳比保存在时间戳中的时间戳更新,这些进程就会更新实现本地数据 R-i,并且,在所有情况下,它都会发送回P和确认,因此 ,P终止了它的写操作。

    因此,调用进程P与值 v 相关联的时间戳大于在P发出写操作之前的写操作时间戳。此外,虽然并发写操作可以将相同的序列号与它们的值关联,但是这些值具有不同的有序时间戳。异步消息系统中实现原子读/写寄存器也是串行计算在抽象层上的使用。

    d000ee6b6fa08f95abcc0310e3e683cb.png

    并发对象

    读/写寄存器是一种特殊的对象。一般来说,一个对象是由进程可以调用的一组操作定义的,当这些操作按顺序调用时,对象的行为预先定义好的。这些可以用状态机或一组顺序标识来表示。因此,可以使用串行计算中常见的数据结构(如队列和堆栈)来定义并发对象。

    在许多使用串行计算的并发编程(包括状态机复制)中,其核心是协议问题。一个常见的基础抽象是一致性对象。如果, C是一个一致性对象,进程P调用操作 C.propose (v)一次,则最终返回一个值 v’。C的这个顺序规范是由以下属性定义的:

    • 如果调用返回 v,则存在 C.propose (v);

    • 不返回两个不同的值;

    • 如果一个进程调用 C.propose (v)并且没有崩溃,那么该操作将返回一个值。

    在异步或者易崩溃的环境中,所有对象并不相同。一致性对象是最强大的,因为它们可以用来实现由串行计算定义的任何对象。其他对象,如队列或堆栈具有中等强度,它们不能由只使用读/写寄存器进行通信的异步进程实现。这些实现要求进程调用的任何操作必须返回,无需等待。

    在存在异步通信和进程崩溃的情况下,对象同步能力的一种测量方法是它的共识数量。如果对象 o 的共识数是整数 n,那么,从任意数量的对象 o 和原子读/写寄存器实现 n 个进程的一致性对象,例如,Set 对象或堆栈对象的共识数为2。

    0fcf218bc812ebcc06906b54fe229850.png

    状态机复制

    并发堆栈可以通过使用互斥锁执行 pop ()和 push ()操作来实现。但是,如果进程崩溃,这种策略将不起作用。状态机复制机制是通过异步进程通信实现的一种通用方法。其基本思想是让进程在并发调用的顺序上达成一致,然后每个进程在本地模拟串行计算的状态机。

    假设把To-broadcast 抽象为分布式计算中的一个原语,它确保所有正确的进程以相同的顺序接收消息。进程调用 Tobroadcast (m) ,向所有其他进程发送消息 m,那么,进程在收到完全有序的消息时执行 Todeliver ()。在基于串行计算的并发编程中,To-broadcast 是一个普遍的概念,这种通信抽象促进了基于串行计算并发对象的构建。

    对于基于 To-broadcast 的状态机复制而言,每个进程Px都有一个对象的拷贝状态,To-broadcast 抽象用于确保所有进程Px 对其对象 o 的本地状态采用相同的操作序列。实现协商一致的 To-broadcast,如果调用进程在调用期间没有崩溃,则所有流程都会收到 m,如果流程的任意子集收到 m。算法的核心是后台任务,一个进程会一直等待, 会对消息进行排序。

    9ec3755f9831c122c7297d8e72b051b6.png

    区块链中的并发计算

    在区块链网络中,所有参与者都可以拥有自己的分类账副本。它们中的任何一个都可以在分类账中附加一个记录,然后在几分钟甚至几秒钟内反映在所有副本中。使用加密技术,存储在分类账中的记录可以保持防篡改性。

    区块链中典型的分布式分类账,是特定账本对象的一个拜占庭式容错复制实现。账本对象有两个操作,read ()和 append ()。它的串行计算是由一个块列表定义的,可以在列表的末尾添加一个块 x,操作 append (x) ,而 read ()返回整个列表。在加密货币的情况下,x 可能包含一组交易。

    因此,和任何其他对象一样,账本对象可以使用拜占庭容错状态机的复制算法来实现。相反,分类帐可以作为一个通用结构,是一个具有转换函数的状态机定义的对象 o。为此,当进程调用 append (x)时,x 包含一个应用于状态机的转换。对象的状态通过 read ()获得,该调用返回被顺序附加到分类账中的操作序列,然后从对象的初始状态开始在本地应用它们。

    显然,read ()操作返回已应用到状态机的命令列表,保证了列表防篡改的可能性,区块链的实现中使用加密散列将每个记录链接到前一个记录。

    任何人都可以附加块并读取区块链。与通过串行计算掌握并发性的传统算法相反,参与者不必事先知道,可以随时间变化,甚至可能是匿名的。在某种意义上,就是一个开放的分布式数据库,没有信任的权威节点,数据本身分布在参与者之间。

    在状态机复制的框架下,比特币的区块链实现相对简单。从概念上讲,它建立在随机共识的基础上,每当几个进程想要同时添加一个区块时,它们就参与抽签。每个进程在0和某个大整数K之间选择一个随机数,得到小于K的数字的进程获胜,并有权追加其所需的区块。这为通过串行思维控制并发性的范例引入了一个新的想法,在更快的状态机复制和暂时的一致性缺失之间进行权衡。

    2498bc3cc6e6918f4df2798835c9cd62.png

    小结

    在分布式系统中,最终一致性被广泛地部署以实现高可用性数据,最终所有对该数据项的访问都将返回最后更新的值(。在区块链中,通过放松控制并发性的串行控制可以获得的好处,区块链末端的分支暂时违反了分类账对象的一致性。尽管如此,区块链还是受到了性能瓶颈的困扰,因为它需要将所有的交易排序在一个单一的列表中,这促进了部分有序列表的探索,例如基于有向无环图的Tangle 或 Hashgraph。

    CAP 定理形式化了通过顺序推理掌握并发性方法的一个基本限制,另一种选择是可用性成本。只要只有少数程序可能失败,该系统就会继续运作,并维护其一致性保障。

    另外,基于串行计算的并发性方法有一个基本的限制,并非所有并发问题都有顺序规范。事实上,如今我们也没有好的工具来构建高效、可伸缩和可靠的并发系统。

    [关联阅读]

    展开全文
  • 今天小编要说的是《C++并发编程实战》(第2版)这本书,很多程序员都知道这本书。第2版全新翻译,给你一个不一样的阅读体验。 《C++并发编程实战》(第2版)由C++标准委员会成员编写,囊括C++并发编程多个方面...

    今天小编要说的是《C++并发编程实战(第2版)这本书,很多程序员都知道这本书。第2版全新翻译,给你一个不一样的阅读体验。

    C++并发编程实战(第2版)由C++标准委员会成员编写,囊括C++并发编程多个方面。作者Anthony Williams独自起草并参与编写了许多与多线程和并发相关的提案,这些提案塑造了C++标准的一部分。Anthony Williams持续参与了C++标准委员会并发小组的工作,包括对C++17标准进行改进,制定并发技术规约(Concurrency Technical Specification),以及编写关于C++未来演化发展的提案等。

    • 这是一本介绍C++并发和多线编程的深度指南,囊括了C++并发编程的多个方面,涉及启动新线程以及设计全功能的多线程算法和数据结构等核心知识点;
    • 本书译文经过反复推敲,作译者协同参与全书内容的翻译和审读,代码配有详细的中文注释,内容简洁易懂;
    • 译者还基于自己的开发经验,补充了许多延伸知识点,适合想要深入了解C++多线程的开发人员深入学习;
    • 本书提供强大的配套资源,包括近200页的电子版附录D以及140多份配套代码文件。

    本书内容是 C++新标准中涉及的并发与多线程功能,从std::thread、std::mutex和std::async的基本使用方法开始,一直到复杂的内存模型和原子操作。

    假若读者之前没使用过C++11的新功能,那就需要先浏览一下附录A,再开始阅读正文,这将有助于透彻理解本书的代码示例。正文中已经标注出用到C++新特性的地方,尽管如此,一旦你遇到任何从未见过的内容,也可以随时翻查附录。

    如果读者已经编写过多线程代码,并且经验丰富,前几章会让你知晓已经熟知的工具与新标准的C++工具是怎样对应的。倘若读者要进行任何底层工作,涉及原子变量,则第5章必不可少。为了确保读者真正熟知C++多线程编程的各种细节,例如异常安全(exception safety),那么,第8章值得好好学习。如果读者肩负某种特定的编码任务,索引和目录会帮你迅速定位到有关章节。

    即便你已经掌握了C++线程库的使用方法,附录D(可从异步社区下载)依然有用,例如可供你查阅各个类和函数调用的精准细节。你也可以考虑时不时地回顾一下主要章节,或强化记忆某个特定的模型,或重温示例代码。

    简要目录

    • 第1章 你好,C++并发世界
    • 第2章 线程管控
    • 第3章 在线程间共享数据
    • 第4章 并发操作的同步
    • 第5章 C++内存模型和原子操作
    • 第6章 设计基于锁的并发数据结构
    • 第7章 设计无锁数据结构
    • 第8章 设计并发代码
    • 第9章 高级线程管理
    • 第10章 并行算法函数
    • 第11章 多线程应用的测试和除错

    样章试读:第1章 你好,C++并发世界

    1.1 什么是并发

    按最简单、最基本的程度理解,并发(concurrency)是两个或多个同时独立进行的活动。并发现象遍布日常生活,我们时常接触:我们可以边走路边说话;或者,左右手同时做出不一样的动作;我们每个人也都可以独立行事——当我游泳时,你可以观看足球比赛;诸如此类。

    1.1.1 计算机系统中的并发

    若我们谈及计算机系统中的并发,则是指同一个系统中,多个独立活动同时进行,而非依次进行。这不足为奇。多年来,多任务操作系统可以凭借任务切换,让同一台计算机同时运行多个应用软件,这早已稀松平常,而高端服务器配备了多处理器,实现了“真并发”(genuine concurrency)。大势所趋,主流计算机现已能够真真正正地并行处理多任务,而不再只是制造并发的表象。

    很久之前,大多计算机都仅有一个处理器,处理器内只有单一处理单元或单个内核,许多台式计算机至今依旧如此。这种计算机在同一时刻实质上只能处理一个任务,不过,每秒内,它可以在各个任务之间多次切换,先处理某任务的一小部分,接着切换任务,同样只处理一小部分,然后对其他任务如法炮制。于是,看起来所有任务都正在同时执行。因此其被称为任务切换。至此,我们谈及的并发都基于这种模式。由于任务飞速切换,我们难以分辨处理器到底在哪一刻暂停某个任务而切换到另一个。任务切换对使用者和应用软件自身都制造出并发的表象。由于是表象,因此对比真正的并发环境,当应用程序在进行任务切换的单一处理器环境下运行时,其行为可能稍微不同。具体而言,如果就内存模型(见第5章)做出不当假设,本来会导致某些问题,但这些问题在上述环境中却有可能不会出现。第10章将对此深入讨论。

    多年来,配备了多处理器的计算机一直被用作服务器,它要承担高性能的计算任务;现今,基于一芯多核处理器(简称多核处理器)的计算机日渐普及,多核处理器也用在台式计算机上。无论是装配多个处理器,还是单个多核处理器,或是多个多核处理器,这些计算机都能真正并行运作多个任务,我们称之为硬件并发(hardware concurrency)。

    图1.1所示为理想化的情景。计算机有两个任务要处理,将它们进行十等分。在双核机(具有两个处理核)上,两个任务在各自的核上分别执行。另一台单核机则切换任务,交替执行任务小段,但任务小段之间略有间隔。在图1.1中,单核机的任务小段被灰色小条隔开,它们比双核机的分隔条粗大。为了交替执行,每当系统从某一个任务切换到另一个时,就必须完成一次上下文切换(context switch),于是耗费了时间。若要完成一次上下文切换,则操作系统需保存当前任务的CPU状态和指令指针[2],判定需要切换到哪个任务,并为之重新加载CPU状态。接着,CPU有可能需要将新任务的指令和数据从内存加载到缓存,这或许会妨碍CPU,令其无法执行任何指令,加剧延迟。

    图1.1 两种并发方式:双核机上的并发执行与单核机上的任务切换

    尽管多处理器或多核系统明显更适合硬件并发,不过有些处理器也能在单核上执行多线程。真正需要注意的关键因素是硬件支持的线程数(hardware threads),也就是硬件自身真正支持同时运行的独立任务的数量。即便是真正支持硬件并发的系统,任务的数量往往容易超过硬件本身可以并行处理的数量,因而在这种情形下任务切换依然有用。譬如,常见的台式计算机能够同时运行数百个任务,在后台进行各种操作,表面上却处于空闲状态。正是由于任务切换,后台任务才得以运作,才容许我们运行许多应用软件,如文字处理软件、编译器、编辑软件,以及浏览器等。图1.2展示了双核机上4个任务的相互切换,这同样是理想化的情形,各个任务都被均匀切分。实践中,许多问题会导致任务切分不均匀或调度不规则。我们将在第8章探究影响并发代码性能的因素,将解决上述某些问题。

    图1.2 4个任务在双核机上切换

    本书涉及的技术、函数和类适用于各种环境:无论负责运行的计算机是配备了单核单处理器,还是多核多处理器;无论其并发功能如何实现,是凭借任务切换,还是真正的硬件并发,一概不影响使用。然而,也许读者会想到,应用软件如何充分利用并发功能,很大程度上取决于硬件所支持的并发任务数量。我们将在第8章讲述设计并发的C++代码的相关议题,也会涉及这点。

    1.1.2 并发的方式

    设想两位开发者要共同开发一个软件项目。假设他们处于两间独立的办公室,而且各有一份参考手册,则他们可以静心工作,不会彼此干扰。但这令交流颇费周章:他们无法一转身就与对方交谈,遂不得不借助电话或邮件,或是需起身离座走到对方办公室。另外,使用两间办公室有额外开支,还需购买多份参考手册。

    现在,如果安排两位开发者共处一室,他们就能畅谈软件项目的设计,也便于在纸上或壁板上作图,从而有助于交流设计的创意和理念。这样,仅有一间办公室要管理,并且各种资源通常只需一份就足够了。但缺点是,他们恐怕难以集中精神,共享资源也可能出现问题。

    这两种安排开发者的办法示意了并发的两种基本方式。一位开发者代表一个线程,一间办公室代表一个进程。第一种方式采用多个进程,各进程都只含单一线程,情况类似于每位开发者都有自己的办公室;第二种方式只运行单一进程,内含多个线程,正如两位开发者同处一间办公室。我们可以随意组合这两种方式,掌控多个进程,其中有些进程包含多线程,有些进程只包含单一线程,但基本原理相同。接着,我们来简略看看应用软件中的这两种并发方式。

    1.多进程并发

    在应用软件内部,一种并发方式是,将一个应用软件拆分成多个独立进程同时运行,它们都只含单一线程,非常类似于同时运行浏览器和文字处理软件。这些独立进程可以通过所有常规的进程间通信途径相互传递信息(信号、套接字、文件、管道等),如图1.3所示。这种进程间通信普遍存在短处:或设置复杂,或速度慢,甚至二者兼有,因为操作系统往往要在进程之间提供大量防护措施,以免某进程意外改动另一个进程的数据;还有一个短处是运行多个进程的固定开销大,进程的启动花费时间,操作系统必须调配内部资源来管控进程,等等。

    图1.3 两个进程并发运行并相互通信

    进程间通信并非一无是处:通常,操作系统在进程间提供额外保护和高级通信机制。这就意味着,比起线程,采用进程更容易编写出安全的并发代码。某些编程环境以进程作为基本构建单元,其并发效果确实一流,譬如为Erlang编程语言准备的环境。

    运用独立的进程实现并发,还有一个额外优势——通过网络连接,独立的进程能够在不同的计算机上运行。这样做虽然增加了通信开销,可是只要系统设计精良,此法足以低廉而有效地增强并发力度,改进性能。

    2.多线程并发

    另一种并发方式是在单一进程内运行多线程。线程非常像轻量级进程:每个线程都独立运行,并能各自执行不同的指令序列。不过,同一进程内的所有线程都共用相同的地址空间,且所有线程都能直接访问大部分数据。全局变量依然全局可见,指向对象或数据的指针和引用能在线程间传递。尽管进程间共享内存通常可行,但这种做法设置复杂,往往难以驾驭,原因是同一数据的地址在不同进程中不一定相同。图1.4展示了单一进程内的两个线程借共享内存通信。

    图1.4 单一进程内的两个线程借共享内存通信

    我们可以启用多个单线程的进程并在进程间通信,也可以在单一进程内发动多个线程而在线程间通信,后者的额外开销更低。因此,即使共享内存带来隐患,主流语言大都青睐以多线程的方式实现并发功能,当中也包括C++。再加上C++本身尚不直接支持进程间通信,所以采用多进程的应用软件将不得不依赖于平台专属的应用程序接口(Application Program Interface,API)。鉴于此,本书专攻多线程并发,后文再提及并发,便假定采用多线程实现。

    提到多线程代码,还常常用到一个词——并行。接下来,我们来厘清并发与并行的区别。

    1.1.3 并发与并行

    就多线程代码而言,并发与并行(parallel)的含义很大程度上相互重叠。确实,在多数人看来,它们就是相同的。二者差别甚小,主要是着眼点和使用意图不同。两个术语都是指使用可调配的硬件资源同时运行多个任务,但并行更强调性能。当人们谈及并行时,主要关心的是利用可调配的硬件资源提升大规模数据处理的性能;当谈及并发时,主要关心的是分离关注点或响应能力。这两个术语之间并非泾渭分明,它们之间仍有很大程度的重叠,知晓这点会对后文的讨论有所帮助,两者的范例将穿插本书。

    至此,我们已明晰并发的含义,现在来看看应用软件为什么要使用并发技术。

    1.2 为什么使用并发技术

    应用软件使用并发技术的主要原因有两个:分离关注点与性能提升。据我所知,实际上这几乎是仅有的用到并发技术的原因。如果寻根究底,任何其他原因都能归结为二者之一,也可能兼有(除非硬要说“因为我就是想并发”)。

    1.2.1 为分离关注点而并发

    一直以来,编写软件时,分离关注点(separation of concerns)几乎总是不错的构思:归类相关代码,隔离无关代码,使程序更易于理解和测试,因此所含缺陷很可能更少。并发技术可以用于隔离不同领域的操作,即便这些不同领域的操作需同时进行;若不直接使用并发技术,我们将不得不编写框架做任务切换,或者不得不在某个操作步骤中,频繁调用无关领域的代码。

    考虑一个带有用户界面的应用软件,需要由CPU密集处理,如台式计算机上的DVD播放软件。本质上,这个应用软件肩负两大职责:既要从碟片读取数据,解码声音影像,并将其及时传送给图形硬件和音效硬件,让DVD顺畅放映,又要接收用户的操作输入,譬如用户按“暂停”“返回选项单”“退出”等键。假若采取单一线程,则该应用软件在播放过程中,不得不定时检查用户输入,结果会混杂播放DVD的代码与用户界面的代码。改用多线程就可以分离上述两个关注点,一个线程只负责用户界面管理,另一个线程只负责播放DVD,用户界面的代码和播放DVD的代码遂可避免紧密纠缠。两个线程之间还会保留必要的交互,例如按“暂停”键,不过这些交互仅仅与需要立即处理的事件直接关联。

    如果用户发送了操作请求,而播放DVD线程正忙,无法马上处理,那么在请求被传送到该线程的同时,代码通常能令用户界面线程立刻做出响应,即便只是显示光标或提示“请稍候”。这种方法使得应用软件看起来响应及时。类似地,某些必须在后台持续工作的任务,则常常交由独立线程负责运行,例如,让桌面搜索应用软件监控文件系统变动。此法基本能大幅简化各线程的内部逻辑,原因是线程间交互得以限定于代码中可明确辨识的切入点,而无须将不同任务的逻辑交错散置。

    这样,线程的实际数量便与CPU既有的内核数量无关,因为用线程分离关注点的依据是设计理念,不以增加运算吞吐量为目的。

    1.2.2 为性能而并发:任务并行和数据并行

    多处理器系统已存在数十年,不过一直以来它们大都只见于巨型计算机、大型计算机和大型服务器系统。但是,芯片厂家日益倾向设计多核芯片,在单一芯片上集成2个、4个、16个或更多处理器,从而使其性能优于单核芯片。于是,多核台式计算机日渐流行,甚至多核嵌入式设备亦然。不断增强的算力并非得益于单个任务的加速运行,而是来自多任务并行运作。从前,处理器更新换代,程序自然而然随之加速,程序员可以“坐享其成,不劳而获”。但现在,正如Herb Sutter指出的“免费午餐没有了![3]”,软件若要利用增强的这部分算力,就必须设计成并发运行任务。所以程序员必须警觉,特别是那些踌躇不前、忽视并发技术的同业,有必要注意熟练掌握并发技术,储备技能。

    增强性能的并发方式有两种。第一种,最直观地,将单一任务分解成多个部分,各自并行运作,从而节省总运行耗时。此方式即为任务并行。尽管听起来浅白、直接,但这却有可能涉及相当复杂的处理过程,因为任务各部分之间也许存在纷繁的依赖。任务分解可以针对处理过程,调度某线程运行同一算法的某部分,另一线程则运行其他部分;也可以针对数据,线程分别对数据的不同部分执行同样的操作,这被称为数据并行。

    易于采用上述并行方式的算法常常被称为尴尬并行[4]算法。其含义是,将算法的代码并行化实在简单,甚至简单得会让我们尴尬,实际上这是好事。我还遇见过用其他术语描述这类算法,叫“天然并行”(naturally parallel)与“方便并发”(conveniently concurrent)。尴尬并行算法具备的优良特性是可按规模伸缩——只要硬件支持的线程数目增加,算法的并行程度就能相应提升。这种算法是成语“众擎易举”的完美体现。算法中除尴尬并行以外的部分,可以另外划分成一类,其并行任务的数目固定(所以不可按规模伸缩)。第8章和第10章将涵盖按线程分解任务的方法。

    第二种增强性能的并发方式是利用并行资源解决规模更大的问题。例如,只要条件适合,便同时处理2个文件,或者10个,甚至20个,而不是每次1个。同时对多组数据执行一样的操作,实际上是采用了数据并行,其着眼点有别于任务并行。采用这种方式处理单一数据所需的时间依旧不变,而同等时间内能处理的数据相对更多。这种方式明显存在局限,虽然并非任何情形都会因此受益,但数据吞吐量却有所增加,进而带来突破。例如,若能并行处理视频影像中不同的区域,就会提升视频处理的解析度。

    1.2.3 什么时候避免并发

    知道何时避免并发,与知道何时采用并发同等重要。归根结底,不用并发技术的唯一原因是收益不及代价。多数情况下,采用了并发技术的代码更难理解,编写和维护多线程代码会更劳心费神,并且复杂度增加可能导致更多错误。编写正确运行的多线程代码需要额外的开发时间和相关维护成本,除非潜在的性能提升或分离关注点而提高的清晰度值得这些开销,否则别使用并发技术。

    此外,性能增幅可能不如预期。线程的启动存在固有开销,因为系统须妥善分配相关的内核资源和栈空间,然后才可以往调度器添加新线程,这些都会耗费时间。假如子线程上运行的任务太快完成,处理任务本身的时间就会远短于线程启动的时间,结果,应用程序的整体性能很可能还不如完全由主线程直接执行任务的性能。

    再者,线程是一种有限的资源。若一次运行太多线程,便会消耗操作系统资源,可能令系统整体变慢。而且,由于每个线程都需要独立的栈空间[5],如果线程太多,就可能耗尽所属进程的可用内存或地址空间。在采用扁平模式内存架构的32位进程中,可用的地址空间是4GB,这很成问题:假定每个线程栈的大小都是1MB(这个大小常见于许多系统),那么4096个线程即会把全部地址空间耗尽,使得代码、静态数据和堆数据无地立足。尽管64位系统(或指令集宽度更大的系统)对地址空间的直接限制相对宽松,但其资源依旧有限,运行太多线程仍将带来问题。虽然线程池可用于控制线程数量(见第9章),但也非万能妙法,它自身也有局限。

    假设,在服务器端,客户端/服务器(Client/Server,C/S)模式的应用程序为每个连接发起一个独立的线程。如果只有少量连接,这尚能良好工作。不过,请求量巨大的服务器需要处理的连接数目庞大,若采用同样的方法,就会发起过多线程而很快耗尽系统资源。针对这一情形,如果要达到最优性能,便须谨慎使用线程池(见第9章)。

    最后,运行的线程越多,操作系统所做的上下文切换就越频繁,每一次切换都会减少本该用于实质工作的时间。结果,当线程数目达到一定程度时,再增加新线程只会降低应用软件的整体性能,而不会提升性能。正因如此,若读者意在追求最优系统性能,则须以可用的硬件并发资源为依据(或反之考虑其匮乏程度),调整运行线程的数目。

    为了提升性能而使用并发技术,与其他优化策略相仿:它极具提升应用程序性能的潜力,却也可能令代码复杂化,使之更难理解、更容易出错。所以,对于应用程序中涉及性能的关键部分,若其具备提升性能的潜力,收效可观,才值得为之实现并发功能。当然,如果首要目标是设计得清楚明晰或分离关注点,而提升性能居次,也值得采用多线程设计。

    倘若读者已决意在应用软件中使用并发技术,不论是为了性能,还是为了分离关注点,或是因为“多线程的良辰吉日已到”,那么这对C++程序员意义何在?

    1.3 并发与C++多线程

    以标准化形式借多线程支持并发是C++的新特性。C++11标准发布后,我们才不再依靠平台专属的扩展,可以用原生C++直接编写多线程代码。标准C++线程库的成型历经种种取舍,若要掌握其设计逻辑,则知晓其历史渊源颇为重要。

    1.3.1 C++多线程简史

    1998年发布的C++标准并没有采纳线程,许多语言要素在设定其操作效果之时,考虑的依据是抽象的串行计算机(sequential abstract machine)[6]。不仅如此,内存模型亦未正式定义,若不依靠编译器相关的扩展填补C++98标准的不足,就无法写出多线程程序。

    为了支持多线程,编译器厂商便自行对C++进行扩展;广泛流行的C语言的多线程API,如符合POSIX规范的C语言多线程接口[7]和微软Windows系统的多线程API,使得许多C++厂商借助各种平台专属的扩展来支持多线程。这种来自编译器的支持普遍受限,在特定平台上只能使用相应的C语言API,并且须确保C++运行库(譬如异常处理机制的代码)在多线程场景下可以正常工作。尽管甚少编译器厂商给出了正规的多线程内存模型,但编译器和处理器运作优良,使数量庞大的多线程程序得以用C++写就。

    C++程序员并不满足于使用平台专属的C语言API处理多线程,他们更期待C++类库提供面向对象的多线程工具。应用程序框架(如微软基础类库[8])和通用的C++程序库(如Boost和自适配通信环境[9])已累积开发了多个系列的C++类,封装了平台专属的底层API,并提供高级的多线程工具以简化编程任务。尽管C++类库的具体细节千差万别,特别是在启动新线程这一方面,但这些C++类的总体特征有很多共同之处。例如,通过资源获取即初始化(Resource Acquisition Is Initialization,RAII)的惯用手法进行锁操作,它确保了一旦脱离相关作用域,被锁的互斥就自行解开。这项设计特别重要,为许多C++类库所共有,使程序员受益良多。

    现有的C++编译器在许多情况下都能支持多线程,再结合平台专属的API以及平台无关的类库,如Boost和ACE,为编写多线程的C++代码奠定了坚实的基础。于是,无数多线程应用的组件由C++写成,代码量庞大,以百万行计。不过它们缺乏统一标准的支持,这意味着,由于欠缺多线程内存模型,因此在某些情形下程序会出现问题,下面两种情形尤甚:依赖某特定的处理器硬件架构来获得性能提升,或是编写跨平台代码,但编译器的行为却因平台而异。

    1.3.2 新标准对并发的支持

    随着C++11标准的发布,上述种种弊端被一扫而空。C++标准库不仅规定了内存模型,可以区分不同线程,还扩增了新类,分别用于线程管控(见第2章)、保护共享数据(见第3章)、同步线程间操作(见第4章)以及底层原子操作(见第5章)等。

    前文提及的几个C++类库在过往被使用过程中积累了很多经验,C++11线程库对它们颇为倚重。具体而言,新的C++库以Boost线程库作为原始范本,其中很多类在Boost线程库中存在对应者,名字和结构均一致。另外,Boost线程库自身做了多方面改动,以遵循C++标准。因此,原来的Boost使用者应该会对标准C++线程库深感熟悉。

    正如本章开篇所述,C++11标准进行了多项革新,支持并发特性只是其中之一,语言自身还有很多的改进,以便程序员挥洒自如。虽然这些改进普遍超出本书范围,但其中一部分直接影响了C++线程库本身及其使用方式。附录A将简要介绍这些C++新特性。

    1.3.3 C++14和C++17进一步支持并发和并行

    C++14进一步增添了对并发和并行的支持,具体而言,是引入了一种用于保护共享数据的新互斥(见第3章)。C++17则增添了一系列适合新手的并行算法函数(见第10章)。这两版标准都强化了C++的核心和标准程序库的其他部分,简化了多线程代码的编写。

    如前文所述,C++标准委员会还发布了并发技术规约,详述了对C++标准提供的类和函数所做的扩展,特别是有关线程间的同步操作。

    C++明确规定了原子操作的语义,并予以直接支持,使开发者得以摆脱平台专属的汇编语言,仅用纯C++即可编写出高效的代码。对于力求编写高效且可移植的代码的开发者,这简直如有神助:不但由编译器负责处理平台的底层细节,还能通过优化器把操作语义也考虑在内,两者联手改进性能,使程序的整体优化效果更上一层楼。

    1.3.4 标准C++线程库的效率

    通常,对于从事高性能计算工作的开发者,无论是从整体上考量C++,还是就封装了底层工具的C++类而言(具体来说,以新的标准C++线程库中的类为例),他们最在意的因素通常是运行效率。若要实现某项功能,代码可以借助高级工具,或者直接使用底层工具。两种方式的运行开销不同,该项差异叫作抽象损失[10]。如果读者追求极致性能,清楚这点便尤为重要。

    在设计C++标准库和标准C++线程库时,C++标准委员会对此非常注意。其中一项设计目标是,假定某些代码采用了C++标准库所提供的工具,如果改换为直接使用底层API,应该不会带来性能增益,或者收效甚微。因此,在绝大多数主流平台上,C++标准库得以高效地实现(低抽象损失)。

    总有开发者追求性能极限,恨不得下探最底层,亲手掌控半导体器件,以穷尽计算机的算力。C++标准委员会的另一个目标是,确保C++提供充足的底层工具来满足需求。为此,新标准带来了新的内存模型,以及全方位的原子操作库,其能直接单独操作每个位、每个字节,还能直接管控线程同步,并让线程之间可以看见数据变更。过去,开发者若想深入底层,就得选用平台专属的汇编语言;现在,在许多场合,这些原子型别和对应的操作都足以取而代之。只要代码采用了新标准的数据型别与操作,便更具可移植性,且更容易维护。

    C++标准库还提供了高级工具,抽象程度更高,更易于编写多线程代码,出错机会更少。使用这些工具必须执行额外的代码[11],所以有时确实会增加性能开销,但这种性能开销不一定会引发更多抽象损失。与之相比,实现同样的功能,手动编写代码所产生的开销往往更高。此外,对于上述绝大部分额外的代码,编译器会妥善进行内联。

    针对某种特定的使用需求,一些高级工具提供了所需功能,有时还给出了额外功能,超出了原本的需求。在大多情况下,这都不成问题:未被使用的功能完全不产生开销。只有在极少数情况下,这些未被使用的功能会影响其他代码的性能。若读者追求卓越性能,无奈高级工具的开销过大,那最好还是利用底层工具亲自编写所需功能。在绝大部分情况中,这将导致复杂度和出错的可能性同时大增,而性能提升却十分有限,得益远远不偿所失。有时候,即便性能剖析表明瓶颈在于C++标准库的工具,但根本原因还是应用程序设计失当,而非类库的实现欠佳。譬如,如果太多线程争抢同一个互斥对象,就会严重影响性能。与其试图压缩互斥操作以节省琐碎时间,不如重新构建应用程序,从根本上减少对互斥对象的争抢,收效很可能更明显。第8章会涵盖上述议题:如何设计并发应用,减少资源争抢。

    C++标准库还是有可能无法达到性能要求,无法提供所需的功能,但这种情况非常少,一旦出现这种情况,就似乎有必要使用平台专属的工具了。

    1.3.5 平台专属的工具

    虽然标准C++线程库给出了相当全面的多线程和并发工具,但在任何特定的平台上,总有平台专属的工具超额提供标准库之外的功能。为了可以便捷利用这些工具,同时又能照常使用标准C++线程库,C++线程库的某些型别有可能提供成员函数native_handle(),允许它的底层直接运用平台专属的API。因其本质使然,任何采用native_handle()的操作都完全依赖于特定平台,这也超出了本书的范围(以及C++标准库自身的范围)。

    展开全文
  • 我是全网最硬核的高并发编程作者,CSDN最值得关注博主,全网最硬核的基于可视化的多数据源数据异构中间件作者,也许现在就是,也是不久的将来就是,大家同意吗?

    原创不易,小伙伴们给个一键三连呀~~

    大家好,我是冰河~~

    正如文章标题所言,我是全网最硬核的高并发编程作者,CSDN最值得关注博主,全网最硬核的基于可视化的多数据源数据异构中间件作者,也许现在就是,也是不久的将来就是。

    为啥这样说?因为我写了一本全网首部完全免费并且开源的,全方位涵盖源码分析、基础案例、实战案例、面试和系统架构的《深入理解高并发编程》电子书,发行四个月全网累计下载17万+。 而且,我在CSDN上写了近1700篇超硬核原创技术文。我开源的基于可视化的多数据源数据异构中间件mykit-data超30家公司采用,承担着其核心业务数据的全量、增量(定时、实时)同步,经受住了生产环境中大数据量场景下的全量、增量(定时、实时)同步的考验。 其他的,且听我慢慢道来。

    有很多小伙伴是最近才关注我的,对我的了解不多,那我就先厚着脸皮来个自我介绍吧。

    自我介绍

    我是冰河,《海量数据处理与大数据技术实战》,《MySQL技术大全:开发、优化与运维实战》畅销书作者,CSDN博客专家,mykit系列开源框架作者,基于最终消息可靠性的开源分布式事务框架mykit-transaction-message作者,基于可视化多数据源数据异构中间件mykit-data作者。多年来致力于分布式系统架构、微服务、分布式数据库、分布式事务与大数据技术的研究。在高并发、高可用、高可扩展性、高可维护性和大数据等领域拥有丰富的架构经验。对Hadoop,Storm,Spark,Flink等大数据框架源码进行过深度分析,并具有丰富的实战经验,目前在研究云原生领域。

    我的CSDN唯一博客主页:https://binghe.blog.csdn.net/ ,我在CSDN上更新了将近1700篇硬核原创技术文。

    在这里插入图片描述
    我在CSDN发表的博文内容涵盖架构、研发、后端、前端、测试、运维、渗透、大数据、云计算、云原生等多个领域。

    在这里插入图片描述

    我也是《海量数据处理与大数据技术实战》,《MySQL技术大全:开发、优化与运维实战》两本畅销书的作者。而且写这两本畅销书我只用了5个月时间,一本500多页,一本700多页,从基础、开发、优化、运维到架构,全程实战干货。我个人觉得我可能是业界写书最快的作者(应该就是最快的吧,你们说呢?)。

    在这里插入图片描述

    同时,业余时间我也维护着一些个人的开源项目,每个开源项目都是我个人在业余时间持续更新和维护,比较知名的有:

    GitHub:
    文章收录:
    https://github.com/sunshinelyz/technology-binghe
    可视化多数据源异构中间件:
    https://github.com/sunshinelyz/mykit-data
    分布式序列号(ID)生成器:
    https://github.com/sunshinelyz/mykit-serial
    基于最终消息可靠性分布式事务框架:
    https://github.com/sunshinelyz/mykit-transaction-message
    精准延迟任务与消息队列框架:
    https://github.com/sunshinelyz/mykit-delay

    Gitee:
    文章收录:
    https://gitee.com/binghe001/technology-binghe
    可视化多数据源异构中间件:
    https://gitee.com/binghe001/mykit-data
    分布式序列号(ID)生成器:
    https://gitee.com/binghe001/mykit-serial
    基于最终消息可靠性分布式事务框架:
    https://gitee.com/binghe001/mykit-transaction-message
    精准延迟任务与消息队列框架:
    https://gitee.com/binghe001/mykit-delay

    其中,mykit-data可视化多数据源异构中间件已经在超过30家公司的生产环境使用,经受住了生产环境高并发,大流量下的数据全量、增量(定时、实时)同步的考验。

    好了,自我介绍完毕,小伙伴们如果对这些开源项目感兴趣,自己可以下载源码研究下,遇到不懂的问题可以提Issues,也可以在CSDN上私信我,我看到后都会回复大家。

    接下来,我们就聊聊为何我是全网最硬核的高并发编程作者。

    写作背景

    2020年疫情期间,大部分企业都实行居家办公的策略,而我,也在家里办公。这就省去了每天坐公交上下班的时间,无形当中,节省了很多时间,也就意味着有了更多的时间来自由支配。那多出来的时间干啥?想来想去,还是写一些关于高并发编程的技术文章吧,因为在网上公开的关于高并发编程的知识要么很零散,要么就是收费的,根本无法满足我对高并发编程的需求。 怎么办?既然网上没有,那我就自己写吧。

    艰难的写作过程

    整个写作过程其实还是挺艰难的,需要 梳理写作思路,画脑图,看源码,画流程图,写作,排版 等一系列的流程。一篇文章从构思到写作完成真的要付出很多心血。

    开始最难的还是确定【精通高并发系列】专栏的范围,到底要写哪些内容,内容到底要写到何种深度,想来想去,我画了一个简单的脑图,先把要写的并发基础知识写出来。

    开始,我就先写并发编程的三大核心问题:协作、互斥和分工。按照脑图深入并发编程的类库、框架原理和源码。尽管很难,那段时间还是坚持着日更的节奏。每天几乎都是 5点半起床写文,8点吃早饭,9点按时在家上班打卡,下午6点在家下班打卡,吃晚饭。然后晚上7点钟又开始写文,日复一日的坚持。

    写到后面,内容就不止这些了。加入了 源码分析、基础案例、实战案例、面试、系统架构 等关于高并发的硬核知识,真正的实现从原理、源码到实战、面试与系统架构的全方位高并发技术。

    给大家看下就【精通高并发系列】一个专题我写的文章底稿。

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    几乎涉及的所有图片都是我自己手动画出来的。

    在这里插入图片描述

    整理电子书

    写了这么多,后来有小伙伴给我提议,让我整理成电子书,方便他们查看。我觉得这个提议好,于是我又花了将近一周的时间把写的高并发相关的知识整理成了电子书。整理完电子书,又开始纠结了,这本书叫啥名字呢?最终取了一个读者给的名字《深入理解高并发编程(第1版)》。为啥是第1版?没错,就是第1版,因为后面我还要不断的迭代更新它,后续还会推出第2版、第3版、第4版等等。努力把它打造成为业界最厉害的免费开源的高并发编程电子书。

    整部书的结构不变,还是分为: 源码分析、基础案例、实战案例、面试、系统架构 五大部分。

    这部电子书一经问世,没想到短短的四个月时间,累计下载量就已突破17万+。目前下载量仍在持续上升。这部电子书为什么会这么火呢?它到底涵盖哪些内容呢?接下来,我们一探究竟。

    关于电子书

    《深入理解高并发编程(第1版)》这部PDF大部分内容来自冰河的CSDN博客【精通高并发系列】专栏,整体大约36W字,优化排版后共计315页。

    在这里插入图片描述

    涵盖:源码分析篇、基础案例篇、实战案例篇、面试篇和系统架构篇。 这应该是全网最牛的免费开源的高并发编程电子书了吧! 全书结构如下所示。

    全书内容概览

    全书涵盖的内容如下所示。

    在这里插入图片描述

    书籍部分内容如下:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    整部电子书从源码解析到系统架构一气呵成,系统架构篇则作为整部PDF的最后一个部分,在系统架构篇中,我在CSDN博客中挑选了两篇最具代表性的文章《高并发秒杀系统架构解密,不是所有的秒杀都是秒杀 !》和《高并发分布式锁架构解密,不是所有的锁都是分布式
    锁! !
    》。通过对秒杀系统架构和分布式锁架构的深入剖析,使得小伙伴们在更高的思维层次来深入理解高并发编程,并做到在实际项目中灵活运用。

    后续规划

    目前,我也在规划继续更新【精通高并发系列】文章,规划推出《深入理解高并发编程》第2版,第3版等等。我也重新细致的画了高并发编程的脑图。

    在这里插入图片描述

    注意:脑图建议放大后查看。

    后面要做的,就是持续更新文章啦。

    说了这么多,该如何下载这部四个月全网下载量突破17万+的电子书呢?别着急,冰河为你整理好,上传到CSDN啦。

    https://download.csdn.net/download/l1028386804/18209878

    也上传了一份到我的百度网盘。

    链接:https://pan.baidu.com/s/1f81RPMNOyIv3mgc-mTCBQg
    提取码:xhbq

    最后,还是标题那个问题,我是全网最硬核的并发编程作者,是最值得大家关注的CSDN博主,也是全网最硬核的基于可视化的多数据源数据异构中间件作者,大家同意吗?欢迎大家在文末留言交流~~

    如果文章对你有点帮助,小伙伴们给个一键三连~~

    展开全文
  • JAVA并发编程实践

    2018-07-31 16:54:38
    JAVA并发编程实践 JAVA并发编程实践 JAVA并发编程实践
  • 电商中常见的高并发解决方案

    千次阅读 2022-01-17 19:47:29
    本文主要介绍了两种电商项目中常见的高并发解决方案,一种是针对常用并且更新较少的数据做多级缓存,另一种是通过 Nginx 对用户进行限流
  • 当然支持100万并发。 首先,我们必须做出决定,把阅读和写作分开。 然后,它取决于你需要分配多少个单元用于写作和阅读。 我的SQL集群不建议您使用它,因为有太多的错误。 所有这些都需要先进行压力测试。 业务不同...
  • Golang 并发赋值的安全性探讨

    千次阅读 热门讨论 2021-03-22 21:17:28
    我们知道 Golang 中变量的赋值不是并发安全的。 1.什么是并发安全 并发安全就是程序在并发情况下执行的结果是正确的。 比如对一个变量简单的自增操作count++,在非并发下很好理解,而在并发情况下却容易出现预期之外...
  • java高并发程序设计视频全集,并发场景,死锁,活锁,阻塞,非阻塞...
  • PHP实战经验之系统如何支撑高并发

    千次阅读 2021-03-22 19:31:14
    并发系统各不相同。比如每秒百万并发的中间件系统、每日百亿请求的网关系统、瞬时每秒几十万请求的秒杀大促系统。他们在应对高并发的时候,因为系统各自特点的不同,所以应对架构都是不一样的。另外,比如电商平台...
  • 并发操作之——并发编程三要素

    千次阅读 2021-09-04 10:44:27
    并发操作 并发操作之——并发编程三要素。 并发操作之——并发编程三要素并发操作前言一、原子性二、有序性:三、可见性:总结 前言 并发操作之——并发编程三要素。 一、原子性 一个不可再被分割的颗粒,原子性指...
  • 并发是指在一个时间段内有多个进程在执行。 并行指的是在同一时刻有多个进程在同时执行。 如果是在只有一个CPU的情况下,是无法实现并行的,因为同一时刻只能有一个进程被调度执行,如果此时同时要执行其他进程...
  • WebSocket并发实战3.1 整理并发需求3.2 提取性能指标3.3 代码编写 1. WebSocket协议详解 1.1 WebSocket的由来 在《接口测试开发之:一图搞懂HTTP协议与HTTPS协议的传输过程》这篇博文,小鱼分析了一下HTTP和HTTP的...
  • 学习并发编程,没有想象的那么难!!
  • 数据库并发并发异常

    千次阅读 多人点赞 2020-05-24 18:25:41
    本文关键字:脏读、脏写、更新丢失、...在使用数据库来支撑业务系统时,随着用户量的增大,经常会遇到同时读取相同数据的情况,在没有进行并发控制的情况下就会遇到各种各样的问题,对于可能出现的问题我们要有所了解。
  • 很多小伙伴反馈说,高并发专题学了那么久,但是,在真正做项目时,仍然不知道如何下手处理高并发业务场景!图片来自 Pexels甚至很多小伙伴仍然停留在只是简单的提供接口(CRUD)阶段,不知道学习的并发知识如何运用到...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,805,168
精华内容 722,067
关键字:

并发

友情链接: 循迹.zip