本文分享自华为云社区《Python中的多线程与多进程编程大全【python指南】》,作者:柠檬味拥抱。

Python作为一种高级编程语言,提供了多种并发编程的方式,其中多线程与多进程是最常见的两种方式之一。在本文中,我们将探讨Python中多线程与多进程的概念、区别以及如何使用线程池与进程池来提高并发执行效率。

多线程与多进程的概念

多线程

多线程是指在同一进程内,多个线程并发执行。每个线程都拥有自己的执行栈和局部变量,但共享进程的全局变量、静态变量等资源。多线程适合用于I/O密集型任务,如网络请求、文件操作等,因为线程在等待I/O操作完成时可以释放GIL(全局解释器锁),允许其他线程执行。

多进程

多进程是指在操作系统中同时运行多个进程,每个进程都有自己独立的内存空间,相互之间不受影响。多进程适合用于CPU密集型任务,如计算密集型算法、图像处理等,因为多进程可以利用多核CPU并行执行任务,提高整体运算速度。

线程池与进程池的介绍

线程池

线程池是一种预先创建一定数量的线程并维护这些线程,以便在需要时重复使用它们的技术。线程池可以减少线程创建和销毁的开销,提高线程的重复利用率。在Python中,可以使用concurrent.futures.ThreadPoolExecutor来创建线程池。

进程池

进程池类似于线程池,不同之处在于进程池预先创建一定数量的进程并维护这些进程,以便在需要时重复使用它们。进程池可以利用多核CPU并行执行任务,提高整体运算速度。在Python中,可以使用concurrent.futures.ProcessPoolExecutor来创建进程池。

线程池与进程池的应用示例

下面是一个简单的示例,演示了如何使用线程池和进程池来执行一组任务。

import concurrent.futures
import time def task(n):
print(f"Start task {n}")
time.sleep(2)
print(f"End task {n}")
return f"Task {n} result" def main():
# 使用线程池
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
results = executor.map(task, range(5))
for result in results:
print(result) # 使用进程池
with concurrent.futures.ProcessPoolExecutor(max_workers=3) as executor:
results = executor.map(task, range(5))
for result in results:
print(result) if __name__ == "__main__":
main()

在上面的示例中,我们定义了一个task函数,模拟了一个耗时的任务。然后,我们使用ThreadPoolExecutor创建了一个线程池,并使用map方法将任务提交给线程池执行。同样地,我们也使用ProcessPoolExecutor创建了一个进程池,并使用map方法提交任务。最后,我们打印出每个任务的结果。

线程池与进程池的性能比较

虽然线程池与进程池都可以用来实现并发执行任务,但它们之间存在一些性能上的差异。

线程池的优势

  • 轻量级: 线程比进程更轻量级,创建和销毁线程的开销比创建和销毁进程要小。
  • 共享内存: 线程共享同一进程的内存空间,可以方便地共享数据。
  • 低开销: 在切换线程时,线程只需保存和恢复栈和寄存器的状态,开销较低。

进程池的优势

  • 真正的并行: 进程可以利用多核CPU真正并行执行任务,而线程受到GIL的限制,在多核CPU上无法真正并行执行。
  • 稳定性: 进程之间相互独立,一个进程崩溃不会影响其他进程,提高了程序的稳定性。
  • 资源隔离: 每个进程有自己独立的内存空间,可以避免多个线程之间的内存共享问题。

性能比较示例

下面是一个简单的性能比较示例,演示了线程池和进程池在执行CPU密集型任务时的性能差异。

import concurrent.futures
import time def cpu_bound_task(n):
result = 0
for i in range(n):
result += i
return result def main():
start_time = time.time() # 使用线程池执行CPU密集型任务
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
results = executor.map(cpu_bound_task, [1000000] * 3) print("Time taken with ThreadPoolExecutor:", time.time() - start_time) start_time = time.time() # 使用进程池执行CPU密集型任务
with concurrent.futures.ProcessPoolExecutor(max_workers=3) as executor:
results = executor.map(cpu_bound_task, [1000000] * 3) print("Time taken with ProcessPoolExecutor:", time.time() - start_time) if __name__ == "__main__":
main()

在上面的示例中,我们定义了一个cpu_bound_task函数,模拟了一个CPU密集型任务。然后,我们使用ThreadPoolExecutorProcessPoolExecutor分别创建线程池和进程池,并使用map方法提交任务。最后,我们比较了两种方式执行任务所花费的时间。

通过运行以上代码,你会发现使用进程池执行CPU密集型任务的时间通常会比使用线程池执行快,这是因为进程池可以利用多核CPU真正并行执行任务,而线程池受到GIL的限制,在多核CPU上无法真正并行执行。

当考虑如何实现一个能够同时下载多个文件的程序时,线程池和进程池就成为了很有用的工具。让我们看看如何用线程池和进程池来实现这个功能。

首先,我们需要导入相应的库:

import concurrent.futures
import requests
import time

然后,我们定义一个函数来下载文件:

def download_file(url):
filename = url.split("/")[-1]
print(f"Downloading {filename}")
response = requests.get(url)
with open(filename, "wb") as file:
file.write(response.content)
print(f"Downloaded {filename}")
return filename

接下来,我们定义一个函数来下载多个文件,这里我们使用线程池和进程池来分别执行:

def download_files_with_thread_pool(urls):
start_time = time.time()
with concurrent.futures.ThreadPoolExecutor() as executor:
results = executor.map(download_file, urls)
print("Time taken with ThreadPoolExecutor:", time.time() - start_time) def download_files_with_process_pool(urls):
start_time = time.time()
with concurrent.futures.ProcessPoolExecutor() as executor:
results = executor.map(download_file, urls)
print("Time taken with ProcessPoolExecutor:", time.time() - start_time)

最后,我们定义一个主函数来测试这两种方式的性能:

def main():
urls = [
"https://www.example.com/file1.txt",
"https://www.example.com/file2.txt",
"https://www.example.com/file3.txt",
# Add more URLs if needed
] download_files_with_thread_pool(urls)
download_files_with_process_pool(urls) if __name__ == "__main__":
main()

通过运行以上代码,你可以比较使用线程池和进程池下载文件所花费的时间。通常情况下,当下载大量文件时,使用进程池的性能会更好,因为它可以利用多核CPU实现真正的并行下载。而使用线程池则更适合于I/O密集型任务,如网络请求,因为线程在等待I/O操作完成时可以释放GIL,允许其他线程执行。

这个例子展示了如何利用线程池和进程池来提高并发下载文件的效率,同时也强调了根据任务特点选择合适的并发编程方式的重要性。

并发编程中的注意事项

虽然线程池与进程池提供了方便的并发执行任务的方式,但在实际应用中还需要注意一些问题,以避免出现潜在的并发问题和性能瓶颈。

共享资源的同步

  • 在多线程编程中,共享资源的访问需要进行同步,以避免竞争条件和数据不一致性问题。可以使用锁、信号量等同步机制来保护关键资源的访问。
  • 在多进程编程中,由于进程之间相互独立,共享资源的同步相对简单,可以使用进程间通信(如管道、队列)来传递数据,避免数据竞争问题。

内存消耗与上下文切换

  • 创建大量线程或进程可能会导致内存消耗增加,甚至引起内存泄漏问题。因此,在设计并发程序时需要注意资源的合理利用,避免创建过多的线程或进程。
  • 上下文切换也会带来一定的开销,特别是在频繁切换的情况下。因此,在选择并发编程方式时,需要综合考虑任务的特点和系统资源的限制,以及上下文切换的开销。

异常处理与任务超时

  • 在并发执行任务时,需要注意异常处理机制,及时捕获和处理任务中可能出现的异常,以保证程序的稳定性和可靠性。
  • 另外,为了避免任务阻塞导致整个程序停滞,可以设置任务的超时时间,并在超时后取消任务或进行相应的处理。

最佳实践与建议

在实际应用中,为了编写高效、稳定的并发程序,可以遵循以下一些最佳实践和建议:

  • 合理设置并发度: 根据系统资源和任务特点,合理设置线程池或进程池的大小,避免创建过多的线程或进程。
  • 合理分配任务: 根据任务的类型和特点,合理分配任务到线程池或进程池中,以充分利用系统资源。
  • 注意异常处理: 在任务执行过程中及时捕获和处理异常,保证程序的稳定性和可靠性。
  • 监控与调优: 使用监控工具和性能分析工具对并发程序进行监控和调优,及时发现和解决性能瓶颈和潜在问题。

通过遵循以上最佳实践和建议,可以编写出高效、稳定的并发程序,提高程序的执行效率和性能。同时,也可以避免一些常见的并发编程陷阱和问题,确保程序的质量和可靠性。

总结

本文介绍了在Python中使用线程池和进程池来实现并发编程的方法,并提供了相应的代码示例。首先,我们讨论了多线程和多进程的概念及其在并发编程中的应用场景。然后,我们深入探讨了线程池和进程池的工作原理以及它们之间的性能比较。

在代码示例部分,我们演示了如何使用线程池和进程池来执行多个任务,其中包括下载多个文件的示例。通过比较两种方式执行任务所花费的时间,我们可以更好地了解它们在不同场景下的优劣势。

此外,文章还提供了一些并发编程中的注意事项和最佳实践,包括共享资源的同步、内存消耗与上下文切换、异常处理与任务超时等。这些建议有助于开发者编写高效、稳定的并发程序,提高程序的执行效率和性能。

总的来说,线程池和进程池是Python中强大的工具,能够帮助开发者轻松实现并发编程,并充分利用计算资源。选择合适的并发编程方式,并结合实际场景和任务特点,可以编写出高效、可靠的并发程序,提升应用的性能和用户体验。

点击关注,第一时间了解华为云新鲜技术~

一文带你搞清楚Python的多线程和多进程的更多相关文章

  1. 【项目实践】一文带你搞定Spring Security + JWT

    以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...

  2. python的多线程和多进程(一)

    在进入主题之前,我们先学习一下并发和并行的概念: --并发:在操作系统中,并发是指一个时间段中有几个程序都处于启动到运行完毕之间,且这几个程序都是在同一个处理机上运行.但任一时刻点上只有一个程序在处理 ...

  3. python的多线程、多进程、协程用代码详解

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:刘早起早起 PS:如有需要Python学习资料的小伙伴可以加点击下方链 ...

  4. Python之多线程和多进程

    一.多线程 1.顺序执行单个线程,注意要顺序执行的话,需要用join. #coding=utf-8 from threading import Thread import time def my_co ...

  5. Python的多线程和多进程

    (1)多线程的产生并不是因为发明了多核CPU甚至现在有多个CPU+多核的硬件,也不是因为多线程CPU运行效率比单线程高.单从CPU的运行效率上考虑,单任务进程及单线程效率是最高的,因为CPU没有任何进 ...

  6. Python【多线程与多进程】

    import time,threading print("=======串行方式.并行两种方式调用run()函数=======")def run(): print('哈哈哈') # ...

  7. python的多线程、多进程代码示例

    python多进程和多线程的区别:python的多线程不是真正意义上的多线程,由于python编译器的问题,导致python的多线程存在一个PIL锁,使得python的多线程的CPU利用率比预期的要低 ...

  8. selenium +python之多线程与多进程应用于自动化测试

    多线程与多进程与自动化测试用例结合起来执行,从而节省测试用例的总体运行时间. 多线程执行测试测试用例 以百度搜索为例,通过不同的浏览器来启动不同的线程. from selenium import we ...

  9. Python之多线程与多进程(二)

    多进程 上一章:Python多线程与多进程(一) 由于GIL的存在,Python的多线程并没有实现真正的并行.因此,一些问题使用threading模块并不能解决 不过Python为并行提供了一个替代方 ...

  10. Python之多线程与多进程(一)

    多线程 多线程是程序在同样的上下文中同时运行多条线程的能力.这些线程共享同一个进程的资源,可以在并发模式(单核处理器)或并行模式(多核处理器)下执行多个任务 多线程有以下几个优点: 持续响应:在单线程 ...

随机推荐

  1. 问题排查不再愁,Nacos 上线推送轨迹功能

    简介: 微服务体系下,注册中心的调用和配置变更是家常便饭,例如阿里每天就有百万级变更.亿级推送.可是,一旦出现调用或配置异常,问题排查就成了用户最大的困惑:是注册和配置中心导致的,还是上下游业务自身的 ...

  2. Linux系统诊断-内存基础

    简介: Linux系统诊断-内存基础 1. 背景 谈及linux内存,很多时候,我们会关注free,top等基础命令.当系统遇到异常情况时,内存问题的根因追溯,现场诊断时,缺乏深层次的debug能力. ...

  3. 21克:仅需3天,我们就用Quick BI搭建起数据驾驶舱

    ​简介:数智化并不仅仅是大型企业才需要去思考的课题,而是摆在所有企业面前的一个可选项.借助Quick BI搭建的数据分析体系,21克实现了销售.财务.供应链等多部门业务的数据化支撑,从一份份本地化的E ...

  4. [Linux] 日志管理: rsyslogd 服务 (检测启动/自启动/日志位置)

    查看 rsyslogd 服务是否已启动: ps aux | grep rsyslogd 查看 rsyslogd 是否设置了自启动: systemctl status rsyslog 或者 servic ...

  5. 2018-2-13-win10-uwp-自定义控件-SplitViewItem

    title author date CreateTime categories win10 uwp 自定义控件 SplitViewItem lindexi 2018-2-13 17:23:3 +080 ...

  6. 从原始边列表到邻接矩阵Python实现图数据处理的完整指南

    本文分享自华为云社区<从原始边列表到邻接矩阵Python实现图数据处理的完整指南>,作者: 柠檬味拥抱. 在图论和网络分析中,图是一种非常重要的数据结构,它由节点(或顶点)和连接这些节点的 ...

  7. 二、Doris架构原理

    Doris是一个MPP的OLAP系统,以较低的成本提供在大数据集上的高性能分析和报表查询功能. MPP (Massively Parallel Processing),即大规模并行处理.简单来说,MP ...

  8. Linux中的umask

    在Linux中,当创建一个文件或者目录的时候,系统会自动为这个文件或者目录赋予默认的权限,而umask命令就是用来控制这个默认权限的. 查看umask umask的查看有两种方式,一种不带选项-S,一 ...

  9. Oracle中ALTER TABLE的五种用法(三)

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1 ...

  10. 关于armcc中static __inline修饰符的记录

    相关的知识点:内联函数,static关键字 在一次stm32的库代码分析中发现static __inline 的函数定义,对此有些疑惑,static和inline两个关键字为什么要进行连用呢? 对此进 ...