深入了解多线程编程:从并发到并行的转变

深入了解多线程编程:从并发到并行的转变

引言

       在现代软件开发中,多线程编程 是提升性能和响应能力的重要手段。随着多核处理器的普及,单线程应用越来越难以充分利用计算机的处理能力。多线程不仅能够让程序在执行多个任务时显得更加流畅,还能提升 CPU 的利用率,尤其是在处理计算密集型或 IO 密集型任务时。

       然而,多线程编程看似简单,但其中涉及的概念、技术和陷阱却层出不穷。从基础的并发概念到高级的并行处理,理解多线程编程背后的机制将有助于开发者编写更高效、可靠的应用。本文将带你深入探讨多线程编程的各个方面,解析其背后的技术原理,并通过示例代码帮助你快速上手。

1. 并发与并行:不是一个概念
1.1 并发

       首先,我们需要区分并发并行这两个概念。很多开发者在日常开发中可能会将这两个词混为一谈,但它们实际上有着本质的不同。

  • 并发(Concurrency)是指多个任务在同一时间段内交替执行,虽然它们看起来同时进行,但实际是在单个 CPU 核心上轮流处理。例如,操作系统在调度多个任务时会给每个任务分配一个时间片,然后交替执行。这种方式适用于 IO 密集型任务。

  • 并行(Parallelism)则是指多个任务在物理上同时执行。通过多核处理器,程序可以在多个核心上同时运行多个任务,真正实现“同时”执行。这种方式适用于计算密集型任务。

       简而言之,并发解决的是任务切换问题,而并行解决的是任务同时进行的问题。

1.2 线程是并发的基础

       多线程编程就是在并发模型中利用多个线程来同时处理任务。每个线程都是程序中的一个独立执行单元,它们共享进程的内存空间和资源。操作系统通过调度不同线程的执行,来实现多任务的并发处理。

       但是,线程之间共享资源时可能会引发一系列问题,最著名的便是竞争条件(Race Condition)。当多个线程同时访问共享资源时,如果没有合适的同步机制,就可能导致数据不一致或程序崩溃。

2. 多线程的同步与互斥
2.1 共享资源的访问问题

       在多线程程序中,不同线程可能会同时访问相同的数据。假设我们有一个共享变量 counter,多个线程同时对其进行递增操作。若不采取措施,就可能发生竞争条件,最终导致 counter 的值不正确。

import threading

counter = 0

def increment():
    global counter
    for _ in range(1000000):
        counter += 1

threads = []
for _ in range(10):
    thread = threading.Thread(target=increment)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

print(counter)  # 输出的结果可能不为 10000000

       在上面的代码中,counter 变量被多个线程同时访问和修改,最终的结果不一定是预期的 10000000,因为多个线程可能会在同一时刻读取和修改该变量,导致计算错误。

2.2 使用锁解决竞争条件

       为了避免竞争条件的发生,我们可以使用(Lock)来确保同一时间只有一个线程能够访问共享资源。Python 提供了 threading.Lock 类来实现锁机制。我们可以使用 acquire() 方法获得锁,使用 release() 方法释放锁。

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(1000000):
        lock.acquire()  # 获取锁
        counter += 1
        lock.release()  # 释放锁

threads = []
for _ in range(10):
    thread = threading.Thread(target=increment)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

print(counter)  # 输出结果为 10000000

       通过锁机制,确保了同一时刻只有一个线程在修改 counter,从而避免了竞争条件的发生。需要注意的是,锁的使用虽然能确保线程安全,但也可能导致性能瓶颈,因为只有一个线程可以访问临界区,这意味着其他线程必须等待。

2.3 死锁的风险

       在多线程编程中,死锁(Deadlock)是一种常见的问题,指的是多个线程在等待彼此释放锁时陷入无限等待的状态。假设线程 A 持有锁 1,并等待锁 2;同时线程 B 持有锁 2,并等待锁 1。当这两个线程同时发生时,它们就会进入死锁状态。

       为避免死锁,开发者可以采取一些预防措施,例如避免嵌套锁、使用定时锁(如 try_lock)等。

3. 多线程的优化与性能
3.1 线程池:减少线程创建的开销

       线程的创建和销毁是有成本的,尤其是在需要大量线程时。为了提高性能,开发者通常会使用线程池来复用线程,避免频繁创建和销毁线程。

       Python 的 concurrent.futures.ThreadPoolExecutor 类提供了线程池的功能,我们可以通过它来管理线程的生命周期,并且简化多线程编程的复杂性。

from concurrent.futures import ThreadPoolExecutor

def task(n):
    print(f"Processing task {n}")

with ThreadPoolExecutor(max_workers=5) as executor:
    executor.map(task, range(10))

       在上面的代码中,ThreadPoolExecutor 创建了一个线程池,最多并发执行 5 个任务。使用线程池可以有效地控制线程的数量,避免过多线程导致的性能瓶颈。

3.2 避免过度并发

       尽管多线程能提升程序的执行效率,但过度并发反而会导致性能下降。当线程数远超 CPU 核心数时,线程之间的切换成本会增加,反而降低了整体性能。因此,在设计多线程程序时,要根据实际的硬件条件来合理设置并发线程的数量。

       例如,对于 IO 密集型任务,可以使用更多的线程来提升效率,因为线程在等待 IO 操作时并不会占用 CPU;而对于 CPU 密集型任务,线程数应接近 CPU 核心数,以避免过多的线程导致上下文切换的开销。

4. 现代多线程编程:协程与异步编程

       随着异步编程技术的兴起,协程成为了新的“多线程”解决方案。与传统的线程相比,协程通过 事件循环 来实现任务的并发执行,不需要操作系统线程调度器的参与,从而节省了大量的上下文切换开销。

       Python 的 asyncio 库和 async/await 语法使得异步编程变得简单高效。通过协程,可以在单个线程内实现类似多线程的并发处理,特别适合 IO 密集型任务。

import asyncio

async def fetch_data():
    print("Fetching data...")
    await asyncio.sleep(2)
    print("Data fetched!")

async def main():
    await asyncio.gather(fetch_data(), fetch_data(), fetch_data())

asyncio.run(main())

       在上面的代码中,asyncio 通过协程实现了任务的并发执行,而不会消耗额外的线程资源。协程的优势在于能够以极低的成本实现高并发,适用于大量 IO 操作的场景。

结语

       多线程编程不仅能够让我们的应用更高效地利用计算资源,还能提升响应速度和处理能力。然而,它也带来了许多挑战,尤其是在共享资源访问和线程同步方面。

       通过理解并发与并行的概念、掌握线程同步机制以及利用现代的线程池和协程技术,我们能够写出更加高效、可靠的多线程程序。在未来,随着硬件和编程语言的不断发展,程序员们将有更多工具和技术来应对复杂的并发和并行计算问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【DuodooBMS】给PDF附件加“受控”水印的完整Python实现

给PDF附件加“受控”水印的完整Python实现 功能需求 在实际工作中,许多文件需要添加水印以标识其状态,例如“受控”“机密”等。对于PDF文件,添加水印不仅可以增强文件的可识别性,还可以防止未经授权的使用。本代码的功能需求是…

linux的三剑客和进程处理

Linux三剑客: grep:查找 sed:编辑 awk:分析 grep - 正则表达式 [rootlocalhost ~]# grep ^a hello.txt abc grep - 忽略大小写,还有一些场景需要查询出来对应字符串所在的行号,方便我们快速在文件中定位字…

ASUS/华硕飞行堡垒9 FX506H FX706H 原厂Win10系统 工厂文件 带ASUS Recovery恢复

华硕工厂文件恢复系统 ,安装结束后带隐藏分区,带一键恢复,以及机器所有的驱动和软件。 支持型号:FX506HC, FX506HE, FX506HM, FX706HC, FX706HE, FX706HM, FX506HHR, FX706HMB, FX706HEB, FX706HCB, FX506HMB, FX506HEB, FX506HC…

13.StringTable

String的基本特性 String:字符串,使用一对 ”” 引起来表示 String s1 "mogublog" ; // 字面量的定义方式String s2 new String("moxi"); string声明为final的,不可被继承String实现了Serializable接口:表…

JavaSE基本知识补充 -Map集合

目录 Map(key,value键值对呈现) 1.1 Map的映射的特点 1. 2.HashMap (键值对的业务偏多,而且hashmap在jdk1.7和1.8之间有所不同,性能做了提升,面试高频考点) 1.3 Map接口的方法 方法 HashMap遍…

JAVA学习第二天

ArryList的构造方法和添加方法 01。构造方法的<>里面可以放数据类型 02. add&#xff08;&#xff09;可以直接在后面加入数据&#xff0c;也可以指定下标的插入元素。 ArrayList的常用方法 ArrayList存储对象 在Java中&#xff0c;System.out.println()可以打印基本数据…

基于窄带物联网的矿车追踪定位系统(论文+源码+实物)

1.功能设计 鉴于智能物联网的大趋势&#xff0c;本次基于窄带物联网的矿车追踪定位系统应具备以下功能&#xff1a; &#xff08;1&#xff09;实现实时定位&#xff0c;真正实现矿车随时随地定位; &#xff08;2&#xff09;定位精度高&#xff0c;采用该系统可以实现矿车在…

如何把邮件批量导出到本地

最近遇到邮箱满了的问题&#xff0c;需要把邮件批量导出到本地&#xff0c;然后清空邮箱。 问题是这个邮箱的官网&#xff0c;没有批量导出按钮&#xff0c;比较麻烦&#xff1b;总不能一封一封下载到本地&#xff0c;上万的。 找到了一个好用的工具&#xff0c;Mozilla Thun…

ICLR 2025 oral|用nuPlan + 200h物流小车数据集测试!SOTA扩散模型轨迹规划器来了

导读&#xff1a; 本文介绍了清华大学联合毫末智行、自动化所、港中文、上海交大、上海人工智能实验室最新研究成果《Diffusion-based Planning for Autonomous Driving with Flexible Guidance》——荣获ICLR 2025 Oral Presentation(仅1.8%接受率)。 该算法创新性地设计了基…

dify.ai 怎么配置链接火山引擎等云厂商的deepseek模型

要将 dify.ai 配置链接到火山引擎等云厂商的 DeepSeek 模型. 申请火山引擎的key&#xff0c;创建endpoint 添加模型 测试模型

SAP-ABAP:dialog界面中的数据块Event Block详解举例

在SAP的Dialog程序开发中&#xff0c;Event Block&#xff08;事件块&#xff09;是屏幕流逻辑&#xff08;Flow Logic&#xff09;中的关键部分&#xff0c;用于定义屏幕在特定事件触发时执行的逻辑。Event Block通常与ABAP模块&#xff08;Module&#xff09;结合使用&#x…

2025年怎么选择SEO发布工具

在如今竞争激烈的互联网时代&#xff0c;网站的流量和曝光率直接决定着一个品牌或企业的市场影响力。无论是个人博客&#xff0c;还是企业官网&#xff0c;能够有效提升SEO&#xff08;搜索引擎优化&#xff09;排名的工具&#xff0c;已成为许多网站管理者和营销人员的必备良器…

Java 进阶day14XML Dom4j 工厂模式 Base64

目录 知识点1、XML 概念XML约束 知识点2、XML解析 Dom4j&#xff08;Dom for java&#xff09;XPath 知识点3、工厂模式知识点4、Base64 知识点1、XML 概念 XML的全称为&#xff08;eXtensible Markup Language&#xff09;&#xff0c;是一种可扩展的标记语言。 XML的作用…

数据结构实验——排序算法的实现与分析

前言 到目前为止&#xff0c;8个数据结构实验在这里就全部更完啦&#xff08;撒花&#xff09;&#xff01;我那一段难忘的周二晚课时光也告一段落&#xff0c;整体来说&#xff0c;有赶课的折腾&#xff0c;有调错的崩溃&#xff0c;也有故意迟到五分钟的惬意&#xff0c;用G…

【Antv G2 5.x】饼图添加点击事件,获取当前坐标数据

// 监听 tooltip:show 事件this.chart.on(tooltip:show, (event) => {this.currentShowTooltipName = event.data.items[0].name})// 监听绘图区plot的点击事件this.chart.on(interval:click, ev => {this.$emit(chartClick, this.currentShowTooltipName);})// 监听绘图…

Oracle常用导元数据方法

1 说明 前两天领导发邮件要求导出O库一批表和索引的ddl语句做国产化测试&#xff0c;涉及6个系统&#xff0c;6千多张表&#xff0c;还好涉及的用户并不多&#xff0c;要不然很麻烦。 如此大费周折原因&#xff0c;是某国产库无法做元数据迁移。。。额&#xff0c;只能我手动导…

anolis os 8.9安装jenkins

一、系统版本 # cat /etc/anolis-release Anolis OS release 8.9 二、安装 # dnf install -y epel-release # wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo # rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.…

Python办公自动化之PDF

python版本&#xff1a;3.13.1 开发工具&#xff1a;pycharm 安装三方库&#xff1a;pypdf2 、pdfplumber、pymupdf 一、从PDF中提取文字 用Python从PDF中提取文字-CSDN博客 二、从PDF中提取表格 用Python从PDF中提取表格-CSDN博客 三、拆分和合并PDF文件 用Python拆…

变化检测相关论文可读list

一些用得上的&#xff1a; 遥感变化检测常见数据集https://github.com/rsdler/Remote-Sensing-Change-Detection-Dataset/ 代码解读&#xff1a;代码解读 | 极简代码遥感语义分割&#xff0c;结合GDAL从零实现&#xff0c;以U-Net和建筑物提取为例 NeurIPS2024: https://mp.w…

ASP.NET Core SignalR案例:导入英汉词典

Ecdict 下载词典文件stardict.7z&#xff0c;解压&#xff0c;stardict.csv是一个CSV格式的文本文件&#xff0c;文件的第一行是表头&#xff0c;除第一行外&#xff0c;其他每行文本是一个单词的相关信息&#xff0c;用逗号分隔的就是各个列的值。英汉词典ECDICT中导入单词到…