写点什么

如何让代码并发效率更高

  • 2013-08-13
  • 本文字数:2197 字

    阅读完需:约 7 分钟

随着计算硬件的快速发展,多核多处理器已经广泛应用于企业和个人环境中,开发人员利用多线程技术努力提高软件的计算速度,资深系统架构师 Gurudutt Kumar总结了如何让代码并发效率更高的实践经验。

Gurudutt 首先列举了几种影响软件可伸缩性的问题:

  • 效率低下的并行化:单片应用程序或软件无法有效使用可用的计算资源。您需要将应用程序组织成并行任务。在传统的不支持多线程的应用程序或软件中,我们会经常看到这个问题。这些应用程序在多核、多处理器、芯片多线程硬件上无法伸缩,并且无法实现更好的吞吐量。线程太多可能会和线程太少一样,都不会产生好的结果。
  • 串行瓶颈:在多个线程或进程之间共享数据结构的应用程序可能会有串行瓶颈。为了保持数据完整性,可能必须使用锁定和串行化技术(例如,读取锁、读写锁、写入锁、自旋锁、互斥等)将这些共享数据结构的访问串行化。设计得效率低下的锁可能会由于多个线程或进程之间的高度锁争用而导致串行瓶颈,从而尝试获取锁。这可能会潜在地降低应用程序或软件的性能。应用程序的性能可能会随着核心或处理器数量的增加而降低。
  • 对操作系统 (OS) 或运行时环境的过度依赖:您不能依赖操作系统、运行时环境或编译器来完成伸缩应用程序或软件所需的一切操作。但是,编译器和运行时环境可以帮助提供一定的优化,您不能依赖它们解决所有可伸缩性问题。例如,不能依赖 Java™ 虚拟机 (JVM) 通过自动并行来发现 Java 应用程序的最佳可伸缩的机会。
  • 工作负载的不平衡可能是一个瓶颈:工作负载的不均匀分布可能导致无法有效地利用计算资源。您可能必须将较大的任务划分成可以并行运行的较小的任务,还可能必须将串行算法更改为并行算法,以便提高性能和可伸缩性。
  • I/O 瓶颈:由于阻止磁盘输入 / 输出 (I/O) 或高网络延迟而导致的瓶颈可能会严重抑制应用程序的可伸缩性。
  • 无效的内存管理:在多核平台上,因为有很多处理单元,因此纯计算可能非常廉价,并且主要内存可能也不是问题,因为它正在变得越来越大。但是,内存带宽一直是一个瓶颈,因为所有处理器核心都贡献了一个通用的总线。无效的内存管理可能导致一些难以检测到的性能问题,比如伪共享。

以“避免内存争用”为例,Gurudut 做了解释:

在内存和缓存中,各种不同的核共享一个通用的数据区域,这需要在它们之间进行同步。当不同的核同时访问同一个数据区域时,会发生 _ 内存争用 _。在不同的核之间同步数据会因总线通信、锁定成本以及缓存缺失而有很大的性能损失。如果应用程序有多个线程,并且所有线程都更新或修改同一个内存地址,那么正如前面部分所讨论的那样,为了保持缓存一致性,可能会产生一次重大的乒乓效应。这会导致性能降低。

如何避免内存争用呢?Gurudut 认为“不要在核之间共享可写入的状态”:

  • 为了最大程度地减少内存总线通信,可以通过最小化共享位置 / 数据尽可能地减少核心交互,即使共享数据没有锁保护,而有一些硬件级别原子指令(如 Microsoft® Windows® 32 位平台上的 InterlockedExchangeAdd64)保护也是如此。
  • 减少线程之间的内存争用的一个方法是从多个线程中消除对共享内存区域的更新。例如,即便是在多个线程需要更新全局计数器或累计总数(如统计数据)时,各个线程也可以保持线程本地总数,并让全局总数仅在需要时通过一个通用的线程进行更新。因此,在共享内存区域上的争用会大大减少。
  • 趋向于减少锁争用的模式会减少内存通信,因为它是一个共享的可写入状态,该状态需要使用锁并产生争用。

对于锁争用,Gurudut 认为要从“避免”和“减少”两个方面来解决这个问题:

避免

  • 避免在数据结构中发生锁争用的方法之一是采用并发数据结构设计和无锁算法,这会消除锁以及传统的同步技巧(比如互斥)。有多种并发数据结构的设计并不需要利用同步机制,比如互斥。
  • 无锁算法的一些示例如下:
    • 使用 相对论编程 的可伸缩并发哈希表:该技巧的最简单示例是 Read Copy Update (RCU) ,它专用于 Linux 内核,大大提高了 Linux 内核的性能,并简化了 Linux 内核的代码。
    • 无锁可扩展有序分割的哈希列表:这个无锁递归可扩展哈希算法使用了无锁的链接列表,这些列表使用原子指令来修改链接的列表。
  • 在 Linux 内核中,广泛使用了每处理器变量,系统上的每个处理器都获得了自己的一个给定变量的副本。访问每处理器变量不需要使用锁,此外,因为在不同的处理器上,这些变量未在线程之间共享,因此没有伪共享或内存争用。这种技巧非常适合收集统计信息。

减少

  • 当使用传统锁或同步技巧(如自旋锁)时,必须注意的是,不要使用单片锁或全局锁,而是将这些锁分成更细小的部分。因此,锁会保护数据结构中的某个特定区域以及较小的区域。这样多个线程就能够通过获取保护这些成员的相应锁,在同一数据结构的不同成员上并发进行操作。这种方法可以实现更多并发。
  • 甚至当软件设计中的同步机制能够实现更好的并发和减少锁争用时,也可能会由于伪共享而导致发生性能问题。例如,考虑一个哈希数据结构。如果存在一个自旋锁数组,用于保护哈希中的每个哈希桶,那么在自旋锁数组中可能会出现伪共享。两个线程在两个不同的处理器上运行,每个线程都锁定哈希中的不同哈希桶,那么当它们所需的自旋锁位于同一个缓存行上时,可能会发生伪共享。因此,在设计此类算法时需要考虑采用避免发生伪共享的通用技巧。

作者的微信公众号“老崔瞎编”,关注 IT 趋势,承载前沿、深入、有温度的内容。感兴趣的读者可以搜索 ID:laocuixiabian,或者扫描下方二维码加关注。

2013-08-13 03:156581
用户头像

发布了 501 篇内容, 共 283.0 次阅读, 收获喜欢 64 次。

关注

评论

发布
暂无评论
发现更多内容

京东零售重磅开源 | OxyGent:5分钟打造2000+AI智能体军团

京东科技开发者

高德×夸克AI眼镜:抬头即见的智能出行新体验

高德开放平台

loT 高德地图 地图服务 智能眼镜

医护年省120小时!这场静默革命正在抢救医院资产

斯科信息

斯科信息 RFID系统 RFID读写器 RFID智能药柜

全面解读 Vue Vapor的事件机制

OpenTiny社区

开源 Vue 前端 组件 OpenTiny

有没有不会BOM 也能做好生产的攻略啊?抱歉,这真没有!

积木链小链

BOM 智能制造 生产管理

c# ACME client (补充)

八苦-瞿昙

Cursor可控AI编程实践:缩短交付周期,保障产品质量

Fabarta

人工智能 智能体 AI 编程

解析 vLLM 架构及源码系列 - API Server

Jason黄

vLLM vLLM源码

能自定义、能分享的智能体,还支持生成随机头像

火山引擎开发者社区

Trae

没有Happens-Before?你的多线程代码就是‘一锅粥’!

poemyang

并发编程 happens-before #java

[大厂实践] Netflix 时间序列数据抽象层实践

俞凡

架构 大厂实践

开源 Ai Agent 智能体,能用、能改、能学,美滋滋!

京东科技开发者

万字深度解析LangGraph如何解决HIL四大行业级痛点?​

聚客AI学院

agent LLM 大模型安全 AI 智能体 LangGraph

用 AI 启航,Gemini 重塑旅游酒店行业新体验

Cloud Ace 云一

AI+ gemini

定位与专长的分野:ThingsBoard 物联网平台与 MyEMS 能源管理系统的深度对比

开源能源管理系统

开源 能源管理系统

北京理工大学医工交叉教学实践分享(1)|如何以实践破解数据挖掘教学痛点

ModelWhale

AI教育 医工融合 北京理工大学

GitHub 上 Star 数量前 8 的开源 MCP 项目

NocoBase

GitHub 开源 AI LLM MCP

OceanBase PoC 经验总结(二)—— AP 业务

老纪的技术唠嗑局

运维 测试 数据库 大数据

突破风电运维瓶颈!上交大联合昇腾实现联邦学习训练性能飞跃

极客天地

TRAE cue 体验提升之 Latency 篇

火山引擎开发者社区

Trae

意十足!京东JoyAgent智能体确实是产品级开源!

京东科技开发者

TRAE 2.0 SOLO 发布会回顾

火山引擎开发者社区

Trae

二手车平台是否需要过等保?为什么?

行云管家

网络安全 数据安全 过等保

基于深度学习YOLO框架的城市道路损伤检测与评估项目系统【附完整源码+数据集】

程序员Geek

人工智能

2025广州等保测评机构名单看这里!

行云管家

网络安全 等保

数据分析与AI丨F1赛车级数据智能:Altair Panopticon如何助力Prodrive领跑赛事工程?

Altair RapidMiner

人工智能 AI 数据分析 汽车 仿真

谷歌云 | 用 AI 启航,Gemini 重塑旅游酒店行业新体验

Cloud Ace 云一

扣子,正式拥抱开源!

火山引擎开发者社区

AI Agent 扣子

如何让代码并发效率更高_语言 & 开发_崔康_InfoQ精选文章