找回密码
 会员注册
查看: 11|回复: 0

python中的进程线程和协程

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
71619
发表于 2024-9-6 13:54:14 | 显示全部楼层 |阅读模式
目录进程(Process)多进程代码实例线程(Thread)多线程存在原因及其缺点多线程代码实例协程(Coroutine)协程的优点协程代码实例进程、线程和协程适合的任务性质和环境多进程更适合的场景多线程更适合的场景协程更适合的场景在Python编程中,进程、线程和协程是实现并发和并行执行任务的三种主要机制。它们之间的关系如下图所示:下面是它们的简要概述,以及在Python中与它们相关的内容。进程(Process)操作系统对正在运行程序的抽象,这个就是进程(process)。比如运行一个web浏览器,一个text文本,都是运行的一个一个进程。有的人说:进程是程序运行资源的集合。进程是系统资源分配的最小单位等等。从静态的角度来说,进程确实是运行程序的各种资源集合。进程是操作系统分配资源并执行程序的基本单位。每个进程拥有自己的内存空间、数据栈以及其他跟踪执行的辅助数据。进程之间的内存空间是隔离的,因此它们之间的通信需要使用进程间通信机制(如管道、信号、共享内存、套接字等)。操作系统有多个程序运行,那么就有多个进程,如下所示简图在Python中,你可以使用multiprocessing模块来创建进程、管理进程间的通信和同步。multiprocessing.Process:创建一个进程。multiprocessing.Queue、multiprocessing.Pipe:进程间通信。multiprocessing.Pool:用于并行执行任务的进程池。multiprocessing.Value、multiprocessing.Array:进程间共享数据。还有很多其他同步原语,如Lock、Event、Semaphore等。多进程代码实例下面我们展示下使用进程的代码和优势,下面代码是通过使用进程和不使用进程进行四个网页的读取下载,并在最后通过各自的运行时间来进行比较:importrequestsfrommultiprocessingimportPoolimporttimeurls=['https://example.com','https://httpbin.org','https://github.com','https://google.com']defdownload_page(url):print(f"Downloading{url}...")response=requests.get(url)print(f"Downloaded{url}withstatuscode{response.status_code}")#串行下载网页并测量时间defserial_download(urls):start_time=time.time()forurlinurls:download_page(url)end_time=time.time()returnend_time-start_time#并行下载网页并测量时间defparallel_download(urls):start_time=time.time()withPool(4)aspool:pool.map(download_page,urls)end_time=time.time()returnend_time-start_timeif__name__=='__main__':print("Startingserialdownload...")serial_time=serial_download(urls)print(f"Serialdownloadcompletedin{serial_time}seconds.")print("\nStartingparalleldownloadwithmultiprocessing...")parallel_time=parallel_download(urls)print(f"Paralleldownloadcompletedin{parallel_time}seconds.")print("\nComparison:")print(f"Serial:{serial_time}seconds")print(f"Parallel:{parallel_time}seconds")123456789101112131415161718192021222324252627282930313233343536373839运行结果展示如下:线程(Thread)线程是进程中的执行序列,一个进程可以包含多个线程,它们共享进程的内存空间和资源。线程之间的通信因此更加容易,可以直接读写同一进程内的数据。然而,因为这种共享,线程安全成为一个需要注意的问题,需要使用锁和其他同步机制来保证数据的一致性。《操作系统设计与实现》里说:在传统操作系统中,每个进程中只存在一个地址空间和一个控制流(thread)。然后,有些情况下,需要在相同地址空间中有多个控制流并行的运行,就像他们是单独的进程一样(只是他们共享相同的地址空间)。这些控制流通常被称为线程(thread),有时也称为轻量级进程(lightweightprocess)。尽管线程必须在进程中执行,但是线程和进程是可以分别对待处理的两个概念。进程用来集合资源,而线程是CPU调度的实体。线程给进程模型增加的是,允许在同一个进程环境中有多个执行流,这些执行流在很大程度上相对独立。也即是说,在进程中,程序执行的最小单位(执行流)是线程,可以把线程看作是进程里的一条执行流。一个进程里可以有一条或多条线程。在Python中,可以使用threading模块来创建和管理线程。threading.Thread:创建一个线程。threading.Lock、threading.RLock:线程锁。threading.Event、threading.Condition:线程同步。threading.Semaphore:信号量机制。threading.local:线程本地数据。多线程存在原因及其缺点在一个应用程序执行过程中,应用程序里可能会有多种事件执行。而有些事件执行一段时间后可能会被阻塞。如果把应用程序执行事件分解成多个并行运行的线程,即可以让程序设计变得简单,如果有阻塞的,可以把这部分让出行换其他线程执行。还有一个原因是:线程比进程更轻量级。所以线程比进程更加容易创建,销毁。第三个跟第一个有点关系,是关于性能的,若多线程都是CPU密集型的,那么不能获取性能上增强。如果有大量计算和大量I/O处理,那么多线程就可以获取性能上的优势,因为允许多线程重叠执行。多线程的缺点:1、对于多线程来说,进程中的资源是共享的,所以会产生资源竞争。2、当进程中的一个线程崩溃了,会导致这个进程里的其他线程也崩溃。所以有时多进程程序更好,一个进程崩溃不会导致其他进程也崩溃。多线程代码实例下面我们展示下使用线程的代码和优势,下面代码是通过使用线程和使用进程进行四个网页的读取下载,并在最后通过各自的运行时间来进行比较:importrequestsimportthreadingfrommultiprocessingimportPoolimporttime#网站列表urls=['https://example.com','https://httpbin.org','https://github.com','https://google.com']#下载单个页面的函数defdownload_page(url):print(f"Downloading{url}...")response=requests.get(url)print(f"Downloaded{url}withstatuscode{response.status_code}")#使用多线程下载页面并测量时间defthreaded_download(urls):start_time=time.time()threads=[threading.Thread(target=download_page,args=(url,))forurlinurls]forthreadinthreads:thread.start()forthreadinthreads:thread.join()end_time=time.time()returnend_time-start_time#使用多进程下载页面并测量时间defmultiprocessing_download(urls):start_time=time.time()withPool(4)aspool:pool.map(download_page,urls)end_time=time.time()returnend_time-start_timeif__name__=='__main__':print("Startingthreadeddownload...")threaded_time=threaded_download(urls)print(f"Threadeddownloadcompletedin{threaded_time}seconds.")print("\nStartingmultiprocessingdownload...")multiprocessing_time=multiprocessing_download(urls)print(f"Multiprocessingdownloadcompletedin{multiprocessing_time}seconds.")print("\nComparison:")print(f"Threaded:{threaded_time}seconds")print(f"Multiprocessing:{multiprocessing_time}seconds")123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051代码运行结果展示:通过这里的结果,我们可以看到线程比进程所用的时间更短,效率更高,协程(Coroutine)协程是建立在线程之上,一般是语言级别的”多线程“模型,比线程更加的轻量级。有的叫它微线程。它是完全运行在用户态里。协程是在线程之上在进行抽象,它需要线程来承载运行。一个线程可以有多个协程。协程是一种轻量级的、协作式的并发机制。它允许在单个线程内执行多个任务,通过协作而不是抢占来进行任务切换。协程为异步编程提供了更直观和易用的形式,可以有效地用于I/O密集型和高级别的结构化并发任务。在Python中,可以使用asyncio标准库来创建和管理协程。asyncdef:定义一个协程函数。await:在协程中等待另一个协程的结果。asyncio.run():运行最高层级的协程入口点。asyncio.create_task():调度协程的执行。asyncio.Event、asyncio.Lock等:用于协程的同步原语。asyncio.Queue:用于协程间的消息传递。除了上述内容外,第三方库如gevent和greenlet也提供了对协程的支持和优化,但它们的工作方式与asyncio不同。Python中的协程特别适合编写异步I/O操作,如Web服务器、客户端库等,因为它们在等待网络响应或磁盘I/O等操作的过程中可以挂起函数执行,让出控制权,允许其他协程运行。协程线程关系图:协程的优点1、协程栈很小,只有几KB,而线程栈是1M,对比起来,创建大量协程需要的内存更少。2、协程的调度是语言提供的runtime来调度,是在用户空间直接调度,不需要在内核空间和用户空间来回切换,浪费效率。3、能更好的利用cpu的多核,提高程序执行性能。4、避免阻塞,如果协程所在的线程发生了阻塞,那么协程调度器可以把运行在阻塞线程上的协程,调度到其它没有发生阻塞的线程上,继续运行。协程代码实例下面我们展示下使用协程的代码和优势,下面代码是通过使用协程同线程、进程进行四个网页的读取下载,并在最后通过各自的运行时间来进行比较:importtimeimportthreadingimportasyncioimportaiohttpfromconcurrent.futuresimportThreadPoolExecutor,ProcessPoolExecutorurls=['https://example.com','https://httpbin.org','https://github.com','https://google.com']#同步下载页面的函数(用于线程和进程)defdownload_page(url):withaiohttp.ClientSession()assession:withsession.get(url)asresponse:returnf"Downloaded{url}withstatuscode{response.status}"#使用多线程下载页面defthreaded_download(urls):withThreadPoolExecutor(max_workers=4)asexecutor:executor.map(download_page,urls)#使用多进程下载页面defmultiprocessing_download(urls):withProcessPoolExecutor(max_workers=4)asexecutor:executor.map(download_page,urls)#异步下载页面asyncdefasync_download_page(session,url):asyncwithsession.get(url)asresponse:returnf"Downloaded{url}withstatuscode{response.status}"asyncdefasync_download_all_pages():asyncwithaiohttp.ClientSession()assession:tasks=[async_download_page(session,url)forurlinurls]returnawaitasyncio.gather(*tasks)#测量函数执行时间的装饰器deftimeit(method):deftimed(*args,**kw):ts=time.time()result=method(*args,**kw)te=time.time()print(f"{method.__name__}executedin{(te-ts):.2f}seconds")returnresultreturntimed@timeitdefmeasure_threaded():threaded_download(urls)@timeitdefmeasure_multiprocessing():multiprocessing_download(urls)@timeitasyncdefmeasure_asyncio():awaitasync_download_all_pages()#顺序运行三种方法并比较时间if__name__=='__main__':measure_threaded()measure_multiprocessing()asyncio.run(measure_asyncio())1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162进程、线程和协程适合的任务性质和环境多进程更适合的场景1、CPU密集型任务:对于计算密集型操作,多进程通常比多线程更好,这是因为每个进程有自己的GIL,能够在多核处理器上并行运行,实现真正的并行计算。2、内存隔离和安全:进程之间的内存是隔离的,所以对于需要高安全性或内存隔离的任务,使用多进程会更安全,可以防止数据泄露或污染。3、大规模并发和稳定性:对于需要很多并发执行单元,但其中一些可能会因为异常或必须被杀死的任务,进程可能是更好的选择,因为一个进程崩溃不会影响到其他进程,而线程崩溃可能会影响整个应用程序的稳定性。多线程更适合的场景1、I/O密集型任务:如果任务主要是I/O密集型的,例如网络请求或文件读写操作,线程通常能够提供很好的性能,因为当一个线程等待I/O操作时,其他线程可以继续执行。在Python中,虽然全局解释器锁(GIL)限制了同一时刻只有一个线程执行Python字节码,但I/O密集型任务在等待数据时会释放GIL,允许其他线程运行。2、上下文切换开销小:线程比进程有更小的内存占用和更快的创建及上下文切换时间。进程需要更多的资源和时间来创建,因为每个进程有自己独立的地址空间,而线程则共享内存地址空间。3、GIL的影响有限:由于下载任务主要是在等待网络响应,这意味着大部分时间线程并不持有GIL。因此,即使是在CPython这样的环境中,线程也可能是高效的。协程更适合的场景1、更高的I/O效率——非阻塞I/O操作:协程可以在I/O操作等待数据时挂起,并让出CPU控制权给其他协程。这样可以处理大量的并发网络I/O,非常适合开发高效的网络服务器和客户端。2、轻量级任务管理:相对于进程和线程,协程拥有更小的内存占用,因为任务之间共享内存空间并且上下文切换开销很小。这允许程序员创建数以万计的协程而不会大量耗费系统资源。3、简化的异步编程:使用协程库(如Python中的asyncio),可以用顺序的方式编写非阻塞代码,降低了异步编程的复杂性。4、更好的调度控制:协程的调度是在用户空间完成的,这给了程序员更大的灵活性去控制任务执行的顺序。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-10 05:34 , Processed in 0.422605 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表