首页 > 系统相关 >Python入门实战:多线程与多进程

Python入门实战:多线程与多进程

时间:2023-12-24 19:05:37浏览次数:41  
标签:执行 入门 Python 任务 线程 time 进程 多线程


1.背景介绍

随着信息技术的飞速发展,计算机技术也在日新月异的推进着自己的发展趋势。近几年,随着人工智能、云计算等新兴技术的兴起,计算机已经可以处理更加复杂的计算任务,如图像识别、语音识别、机器学习、数据分析等。由于这些新兴技术的需求驱动,传统的单机应用正在慢慢被替换成分布式、云端应用。因此,为了应对海量的数据计算、高并发的业务场景,程序员们开始寻求新的编程方式——多线程与多进程。

多线程与多进程,是指应用程序同时执行多个任务的方式。与单进程或者多进程相比,多线程或者多进程能够提供并发执行的能力,但由于线程之间共享内存,所以实现起来会比较复杂。对于简单的任务,使用单线程或单进程就能很好地完成工作,但是对于一些耗时计算密集型的任务,多线程与多进程往往会提升效率,并且能够更加灵活地控制资源的分配。

本文将向读者介绍Python中多线程与多进程的相关知识,并通过一个简单的案例展示如何利用多线程实现多任务调度。希望读者通过阅读本文后能够掌握Python中的多线程与多进程编程技巧,能够在实际项目开发中运用多线程、多进程及相应编程模式来提高编程效率、解决性能瓶颈问题。

2.核心概念与联系

2.1 进程(Process)

进程是程序执行时的实例,它是操作系统所创建的最小执行单元。系统运行时,操作系统除了为其创建的基本进程外,还会创建各种附属进程(例如负责监视系统资源和管理子进程等)。一般情况下,每个进程都拥有一个独立的地址空间,用于存放该进程的所有数据。

在Unix系统中,进程是一个具有唯一ID号的轻量级任务,由指令、数据和系统资源组成。当创建一个进程时,系统从可执行文件加载程序代码到内存中,然后创建一个独立的地址空间(代码段、数据段、堆、栈),并为其分配一个唯一的PID。进程通常包括以下几个要素:

  1. 进程ID(process ID):每一个进程都有一个唯一的标识符,称作进程ID(PID),用来标识这个进程;
  2. 程序代码(program code):进程的指令集合;
  3. 进程映像(process image):进程的代码段、数据段和其他资源;
  4. 程序状态(program state):进程中的变量的值、运行栈、寄存器等。

2.2 线程(Thread)

线程是操作系统能够进行运算调度的最小单位。它是进程的一部分,是CPU执行的基本单位,占用独立的栈和寄存器。一个进程可以由多个线程组成,它们共享进程的内存空间,但各自有自己的程序计数器、栈、局部变量和其他运行上下文,彼此之间互不干扰。线程有自己的线程ID,也称作TID。一个线程只能隶属于一个进程,而一个进程可以由多个线程组成。

线程在创建、切换、销毁时,系统都会进行必要的切换和保护,使得应用程序看起来好像只有一个线程在跑。同样地,线程间也可以共享进程的资源,但为了防止线程之间的干扰,需要采取必要的同步机制。

2.3 区别

1、创建开销:

  1. 创建进程的过程比较复杂,涉及许多系统资源的复制和映射,开销较大;
  2. 创建线程的过程比较简单,只需复制少量数据,开销较小;

2、执行开销:

  1. 在创建进程时,所有代码均被拷贝至内存中,运行之前需要执行一次链接过程;
  2. 在创建线程时,仅仅拷贝进程中的少量数据,并不会消耗过多系统资源;

3、内存开销:

  1. 进程占据一个完整的内存空间,包含了进程的所有信息;
  2. 线程仅仅占据少量内存,包含了线程的运行信息,以及指向进程内存空间的指针;

4、通信:

  1. 同一进程内的线程可以直接读写对方的内存空间;
  2. 不同进程之间的线程需要通过IPC进行通信。

3.核心算法原理和具体操作步骤以及数学模型公式详细讲解

在具体例子中,我们先实现两个耗时任务task1()task2(),然后启动三个线程分别执行这两个任务,最终打印出执行时间结果。下面我们依次来看一下各个步骤的实现。

3.1 模拟两个耗时任务

import time 

def task1():
    # simulate a costly operation 
    for i in range(10000):
        pass 
def task2():
    # simulate another costly operation
    for i in range(1000000):
        pass

3.2 使用多线程执行任务

from threading import Thread 

t1 = Thread(target=task1) 
t2 = Thread(target=task2) 
start_time = time.time() 
t1.start() 
t2.start() 
t1.join() 
t2.join() 
print("Execution Time: ", time.time()-start_time)

在这里,我们通过Thread()方法创建了两个线程对象t1t2,并设置目标函数为task1()task2()。之后,我们记录当前的时间戳,启动线程t1t2。在启动过程中,主线程等待线程执行完毕后再继续执行。最后,输出执行时间。

3.3 执行结果示例

示例运行结果如下:

Execution Time:  17.609954833984375

可以看到,通过多线程的方式,我们成功地并行执行了两个耗时任务,并计算出了总的执行时间。从输出结果可以看出,总执行时间约为17.6秒左右,远远超过task1()的预期执行时间。这表明,在计算密集型任务上,多线程并不能有效提升运行速度,甚至可能会造成某些线程的阻塞。因此,在实际项目开发中,要根据任务特点选择最适合的并发方案。

4.具体代码实例和详细解释说明

上面我们通过一个简单的案例演示了Python中多线程与多进程的编程方法。下面我们结合实际案例详细探讨一下具体的代码实现细节。

4.1 文件下载案例

我们假设有一个Web服务,可以把用户上传的文件下载到本地服务器上,需要设计一个程序模块,可以让多个客户端同时下载同一个文件的副本。

4.1.1 任务划分

首先,我们把任务按照功能、模块、组件等进行划分,可以得到以下四个部分:

  1. 文件下载服务程序:主要包括文件下载、校验、下载统计等功能;
  2. 客户端程序:包括客户端模块、配置文件、命令行接口等;
  3. 服务端程序:包括网络连接、协议解析、数据流传输等功能;
  4. 文件存储模块:包括硬盘存储、数据库存储等功能。

4.1.2 分布式系统设计

在进行分布式系统设计前,我们应该考虑以下几点:

  1. 系统拓扑结构:选择合适的分布式系统拓扑结构,可以是一主多从、一主一备、环形网络等;
  2. 通讯协议:确定通讯协议,可以是HTTP、FTP、SSH等;
  3. 数据传输方式:选择合适的数据传输方式,比如可靠传输保证、流式传输等;
  4. 流程控制:采用合适的流程控制机制,比如基于状态机的流程控制;
  5. 错误恢复:设计错误恢复机制,比如自动重试、手动恢复等;
  6. 数据一致性:对分布式环境下的事务要求高,考虑采用最终一致性模型。

4.1.3 并发下载优化

在实现分布式文件下载系统时,我们可以通过以下方式优化并发下载优化:

  1. 使用代理服务器:文件下载请求可以发送给中间层代理服务器,然后通过缓存来降低服务器压力;
  2. 请求合并:减少客户端和服务器之间连接数,达到合并请求的目的;
  3. 断点续传:在下载失败时,可以根据文件大小及已下载数据,重启下载;
  4. 限流控制:限制客户端上传带宽,避免网络拥塞影响下载;
  5. 加密传输:通过HTTPS或TLS加密传输数据;
  6. 框架选型:选择开源框架或商业软件,提升编程效率、可维护性、可用性。

4.2 编码实现

为了实现并发下载文件功能,我们可以编写如下程序模块:

import requests 
import os 
import multiprocessing as mp 
 
 
class Downloader: 
    def __init__(self, url, file_name): 
        self.url = url 
        self.file_name = file_name 
        
    def download(self): 
        response = requests.get(self.url, stream=True) 
        with open(self.file_name, "wb") as f: 
            for chunk in response.iter_content(chunk_size=1024): 
                if chunk: 
                    f.write(chunk) 
     
    
if __name__ == '__main__': 
    urls = ["https://example.com/a", "https://example.com/b"] 
    file_names = ['a.txt', 'b.txt'] 
     
    pool = mp.Pool(processes=len(urls)) 
     
    try: 
        results = [] 
        start_time = time.time() 
        for idx, url in enumerate(urls): 
            d = Downloader(url, file_names[idx]) 
            result = pool.apply_async(d.download, args=())
            results.append(result) 
             
             
        [r.wait() for r in results] 
     
    except KeyboardInterrupt: 
        print('Interrupted') 
    finally: 
        end_time = time.time() 
        elapsed_time = end_time - start_time 
        print('Elapsed time:', elapsed_time) 
         
         
    if not all([os.path.exists(f) for f in file_names]): 
        raise ValueError('Some files are missing.')

以上程序通过multiprocessing模块构建了一个进程池,并根据传入的文件列表,构造了Downloader类的对象,并异步提交到了进程池中执行。主线程则会等待所有的下载任务执行结束,最后检查是否存在缺失的文件。如果程序正常退出,则输出下载用时时间。

5.未来发展趋势与挑战

随着大数据时代的到来,数据的产生速度越来越快,处理速度却越来越慢。为了提高数据的处理速度,我们可以使用分布式、云计算、并行计算等技术。分布式文件系统可以实现文件分布存储、存储节点自动扩容、负载均衡等功能,在一定程度上缓解单机服务器无法支撑的负载问题。云计算平台可以帮助用户按需购买和释放计算资源,在一定规模下提供强大的计算能力。分布式计算框架可以将计算任务分发到多台计算机,充分利用资源并提升整体性能。

另外,由于操作系统调度任务时存在着复杂性、系统资源竞争等因素,因此多线程、多进程及其他并发编程方式在高并发负载下难免存在问题。为了进一步提升系统并发性能,目前已经有越来越多的研究人员开始关注并发相关问题,如协程、异步I/O、事件驱动、Actor模型、编程语言虚拟机、无锁编程等。

6.附录常见问题与解答

6.1 为什么要学习多线程与多进程?

在程序运行过程中,如果某些任务要长时间运行或等待某些资源,就会导致程序性能下降。比如,我们有两个任务:任务A需要花费10s才能完成,任务B需要花费20s才能完成。如果我们串行运行这两个任务,那么总共需要花费30s才能完成;如果我们使用多线程,就可以在同一时间片中运行任务A和任务B,因此总共只需要花费20s才能完成;如果我们使用多进程,就可以在不同进程中同时运行任务A和任务B,这样即使花费30s也只需要花费10s即可完成。通过使用多线程和多进程,我们可以在满足资源限制的条件下获得更好的性能。

6.2 GIL(Global Interpreter Lock)有什么作用?

GIL是CPython中存在的一个限制。它是CPython解释器的一个特性,它允许在同一时刻只允许一个线程执行字节码。因此,在CPython上运行多线程程序时,同一时刻只允许一个线程执行字节码,其它线程必须等待。虽然这种限制可以提高效率,但是它也是Python的缺陷之一。

6.3 什么是协程?

协程就是一个线程上的微线程,他不是线程的子线程,而是在同一个线程里运行。在Python中,使用yield关键字来定义一个协程,当调用send()方法时,执行流会暂停并返回当前位置的指令指针,在下一次调用send()方法时,程序会从暂停的地方开始执行。

协程是一种以单线程方式运行,但却可以显著减少线程创建和切换的编程模型。使用协程可以轻松实现多任务协作和同步,非常适合用于高并发环境。

6.4 asyncio是什么?

asyncio是一个基于PEP 3156为Python标准库引入的新的模块,它提供了异步编程的抽象基类,用来编写高效的、可扩展的、可用的服务器程序。其核心是一个事件循环,事件循环可以同时管理多个任务的执行,协程使用事件循环来运行,可以理解为Python的一个轻量级的异步IO库。

asyncio提供了诸如tcp连接、文件IO、子进程管理等异步API,并提供了一套工具和语法糖,使得异步编程变得十分容易。

6.5 对比与分析

  1. 多线程和多进程的主要区别在于创建、撤销、切换和通信方面。创建:多线程需要自己管理线程的生命周期,线程栈的大小等参数,并在多核系统下需要特殊处理;多进程则完全由操作系统完成,不需要自己去考虑这些事情;
  2. 并行与并发:并行是指两个或多个任务在同一时刻同时运行,也就是说这几个任务都是并发执行的;而并发是指两个或多个任务交替执行,在任意时刻只有一个任务处于运行状态。在单核CPU上,多进程只能增加CPU的利用率,无法真正发挥多核CPU的优势;而在多核CPU上,多线程可以有效利用多核CPU的优势。
  3. GIL的作用是为了保证同一时刻只允许一个线程执行字节码,因此在多线程编程中,会出现死锁和其它一些性能上的问题;协程是基于线程,但又不受GIL的限制,因此在某种程度上可以说是一种折中方案。


标签:执行,入门,Python,任务,线程,time,进程,多线程
From: https://blog.51cto.com/universsky/8956533

相关文章

  • SQL入门让你的数据库升华为强大的搜索引擎
    作者:禅与计算机程序设计艺术1.背景介绍近年来,基于互联网、移动互联网、社交网络等新型信息传播技术的兴起,以及云计算技术的普及,使得数据量和数据类型不断增长,数据的存储成本越来越低廉,能够承载海量数据的服务器的出现。同时,基于数据分析的搜索引擎的兴起,也使得数据成为搜索的主要输......
  • Python算法——最近公共祖先
    Python中的最近公共祖先(LowestCommonAncestor,LCA)算法详解最近公共祖先(LowestCommonAncestor,LCA)是二叉树中两个节点的最低共同祖先节点。在本文中,我们将深入讨论最近公共祖先问题以及如何通过递归算法来解决。我们将提供Python代码实现,并详细说明算法的原理和步骤。最近公共祖先......
  • Python从入门到实践project Web 应⽤程序 Django ⼊门.2
    projectWeb应⽤程序Django⼊门1.创建网页:学习笔记主页2.创建其他网页创建网页:学习笔记主页映射URLfromdjango.urlsimportpath,includepath('',include('learning_logs.urls')),"""定义learning_logs的URL模式"""fromdjango.urlsimportpath......
  • SciTech-Python-编译Python的C/C++扩展的setup.py
    https://github.com/google-deepmind/tree/setup.py"""Setupforpippackage."""importos,platform,sys,sysconfig,shutil,subprocess,setuptoolsfromsetuptools.commandimportbuild_exthere=os.path.dirname(os.path.abspath......
  • Python教程(17)——python模块是什么?python模块详解
    Python模块简介模块是一个包含了Python定义和语句的文件,可用于将功能组织成可重用和可维护的代码块。每个Python文件都可以作为一个模块,模块可以包含变量、函数、类或可执行代码。通过使用模块,我们可以将代码分离成逻辑单元,促进模块化编程。所以我们可以简单的理解为,一个py文件就......
  • Python3.12新增内容
    https://medium.com/techtofreedom/5-handy-python-3-12-new-features-that-improve-your-coding-experience-fe2d6e1f05b4类型系统,更方便的类型别名声明方式简便的类型别名声明我们可以直接如下定义类型别名Point=tuple[float,float]classPointTest: def__init__(s......
  • Python教程(16)——lambda表达式详解
    lambda函数介绍我们平时经常可以在Python的代码中看到一种lambda开头的这种表达式,如果没有学过Python的相关知识,可能会一脸懵逼,不清楚到底这个关键字是干嘛的,用来表示什么。实际上这个就是lambda函数。lambda函数是Python中一种特殊的匿名函数,但不仅仅只存在Python中,它允许我们......
  • Python教程(16)——lambda表达式详解
    lambda函数介绍我们平时经常可以在Python的代码中看到一种lambda开头的这种表达式,如果没有学过Python的相关知识,可能会一脸懵逼,不清楚到底这个关键字是干嘛的,用来表示什么。实际上这个就是lambda函数。lambda函数是Python中一种特殊的匿名函数,但不仅仅只存在Python中,它允许我们......
  • Python爬虫知识点(bs/find_all/正则表达式)
    格式输出 BeautifulSoup库  信息提取  正则表达式     ......
  • Python面试题汇总
    1、一行代码实现1-100之和利用sum()函数求和sum(range(1,101))2、如何在一个函数内部修改全局变量利用global将函数内的变量指定成全局变量a=5deffn():globalaa=4fn()print(a)结果是4,不加global,则解决是53、列出5个python标准库sys:提供了不少与操作系统相关......