本文目录一览:
进程,线程,协程,同步,并发,异步
1,我们公司一开始带饭的人不是很多,公司只有一个茶水间,于是行政的小姐姐很体贴的买了一个微波炉放到茶水间,然后大家中午可以在茶水间排队去热饭。很开心。
2,后来公司发展越来越好,不断有新鲜的血液注入到公司,充满了活力,带饭的人越来越多,行政小姐姐发现一个微波炉根本不够用,于是又卖了两个,大家中午继续去茶水间排队去热饭,速度比以前快了许多,大大缓解了排队过长的压力。
3,某一天公司上市了,发展一片大好,人员也越来越多,于是公司换了新的办公地点,将原来的一个茶水间扩充到了3个,微波炉每个茶水间放5个,同时雇了一个阿姨,专门辅助员工的一些生活方面的事,让员工可以安安心心工作,于是变成了这样,到中午的时候,员工选择3个茶水间一个,将饭盒放在茶水间里,阿姨负责将饭热好放到指定位置,一个阿姨就可以同时操作多个微波炉,一个热好后取出放入下一个,大大提高了热饭效率,热好的饭可以放到指定位置一会自己来取即可,也可以送到员工的位置,大家中午再也不用排队热饭了,
多个茶水间相当于多进程(放在python也可以理解为多核),大大提高了效率,但同时开销也很多,增加一个茶水间的代价远大于增加一个微波炉。
进程,直观点说,保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体 有自己独立的地址空间,有自己的堆 ,上级挂靠单位是操作系统。 操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是资源分配的最小单位 。
大家排队去茶水间热饭,先到的先热,同一茶水间同时只有一个人在热饭。即使有多个微波炉也是顺序的开始工作,三个微波炉相当于三个线程,同时可以热三份饭,但热饭的人是顺序进入茶水间的。
线程,有时被称为轻量级进程(Lightweight Process,LWP),是操作系统调度(CPU调度)执行的最小单位 。
阿姨可以同时操作多个微波炉,一个热好后取出放入下一个,大大提高了热饭效率,哪个微波炉热好就先用哪个,所以协程是无序的,大大提高了工作效率。
一个阿姨在多个茶水间和微波炉工作 充分利用多核
协程,是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。
协程在子程序内部是可中断的,然后转而执行别的子程序,在适当的时候再返回来接着执行 。
阿姨热好饭之后放到指定位置,每个人可以根据自己的时间过来取,如果阿姨空闲了也可以送到员工工位。
公司初期人员较少,所有员工在唯一的一个茶水间排队使用同一个微波炉,顺序使用微波炉。
公司中期增加了微波炉的数量,所有员工在茶水间排队使用多个微波炉,多个微波炉同时工作。
公司发展后期增加了茶水间和微波炉的数量,只有一个阿姨使用多个茶水间的微波炉,只有在微波炉可以使用的条件下才去使用,其他时间可以干其他事情。
【区别】:
调度 : 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位 ;
并发性 : 不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行 ;
拥有资源 : 进程是拥有资源的一个独立单位,线程不拥有系统资源 ,但可以访问隶属于进程的资源。进程所维护的是程序所包含的资源(静态资源), 如: 地址空间,打开的文件句柄集,文件系统状态,信号处理handler等 ;线程所维护的运行相关的资源(动态资源),如: 运行栈,调度相关的控制信息,待处理的信号集等 ;
系统开销 :在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。但是进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个进程死掉就等于所有的线程死掉,所以 多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些 。
【联系】:
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程 ;
资源分配给进程,同一进程的所有线程共享该进程的所有资源;
处理机分给线程,即 真正在处理机上运行的是线程 ;
线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
协程的特点在于是一个线程执行,那和多线程比,协程有何 优势 ?
极高的执行效率 :因为 子程序切换不是线程切换,而是由程序自身控制 ,因此, 没有线程切换的开销 ,和多线程比,线程数量越多,协程的性能优势就越明显;
不需要多线程的锁机制 :因为只有一个线程,也不存在同时写变量冲突, 在协程中控制共享资源不加锁 ,只需要判断状态就好了,所以执行效率比多线程高很多。
(1)进程可使用multiprocessing包实现
与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。
该进程可以运行在Python程序内部编写的函数。
该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。
(2)线程可使用threading包或thread包
(3)协程通过async await 实现,async声明一个函数为异步函数,await可将程序挂起,去执行其他的异步程序。协程 有两种,一种 无栈协程,python 中 以 asyncio 为代表, 一种有栈协程,python 中 以 gevent 为代表。
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
python里怎么实现多个协程一起执行,只要完成
import asyncio
async def phase(i):
print('in phase {}'.format(i))
await asyncio.sleep(0.5 - (0.1 * i))
print('done with phase {}'.format(i))
return 'phase {} result'.format(i)
async def main(num_phases):
print('starting main')
phases = [
phase(i)
for i in range(num_phases)
]
print('waiting for phases to complete')
results = []
for next_to_complete in asyncio.as_completed(phases):
answer = await next_to_complete
print('received answer {!r}'.format(answer))
results.append(answer)
print('results: {!r}'.format(results))
return results
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(3))
finally:
event_loop.close()
在Python中使用Asyncio系统(3-4)Task 和 Future
Task 和 Future
前面我们讨论了协程,以及如何在循环中运行它们才有用。现在我想简单谈谈Task和Future api。你将使用最多的是Task,因为你的大部分工作将涉及使用create_task()函数运行协程,就像在第22页的“快速开始”中设置的那样。Future类实际上是Task的超类,它提供了与循环交互操作的所有功能。
可以这样简单地理解:Future表示某个活动的未来完成状态,并由循环管理。Task是完全相同的,但是具体的“activity”是一个协程——可能是你用async def函数加上create_task()创建的协程。
Future类表示与循环交互的某个东西的状态。这个描述太模糊了,不太有用,所以你可以将Future实例视为一个切换器,一个完成状态的切换器。当创建Future实例时,切换设置为“尚未完成”状态,但稍后它将是“完成”状态。事实上,Future实例有一个名为done()的方法,它允许你检查状态,如示例 3-15所示。
示例 3-15. 用done()方法检查完成状态
Future实例还可以执行以下操作:
• 设置一个result值(用.set_result(value)设置值并且使用 .result()获取值)
• 使用.cancel()方法取消 (并且会用使用.cancelled()检查是否取消)
• 增加一个Future完成时回调的函数
即使Task更常见,也不可能完全避免使用Future:例如,在执行器上运行函数将返回Future实例,而不是Task。让我们快速看一下 示例 3-16 ,了解一下直接使用Future实例是什么感觉。
示例 3-16. 与Future实例的交互
(L3)创建一个简单的 main函数。我们运行这个函数,等上一会儿然后在Future f上设置一个结果。
(L5)设置一个结果。
(L8)手动创建一个Future实例。注意,这个实例(默认情况下)绑定到我们的循环,但它没有也不会被附加到任何协程(这就是Tasks的作用)。
(L9)在做任何事情之前,确认future还没有完成。
(L11)安排main()协程,传递future。请记住,main()协程所做的所有工作就是sleep,然后切换Future实例。(注意main()协程还不会开始运行:协程只在事件循环运行时才开始运行。)
(L13)在这里我们在Future实例上而不是Task实例上使用run_until_complete()。这和你以前见过的不一样。现在循环正在运行,main()协程将开始执行.
(L16)最终,当future的结果被设置时,它就完成了。完成后,可以访问结果。
当然,你不太可能以这里所示的方式直接使用Future;代码示例仅用于教育目的。你与asynccio的大部分联系都是通过Task实例进行的。
你可能想知道如果在Task实例上调用set_result()会发生什么。在Python 3.8之前可以这样做,但现在不允许这么做了。任务实例是协程对象的包装器,它们的结果值只能在内部设置为底层协程函数的结果,如 示例 3-17所示那样。
示例 3-17. 在task上调用set_result
(L13)唯一的区别是我们创建的是Task实例而不是Future实例。当然,Task API要求我们提供一个协程;这里我们使用sleep()只是因为简单方便。
(L7)正在传入一个Task实例。它满足函数的类型签名(因为Task是Future的子类),但从Python 3.8开始,我们不再允许在Task上调用set_result():尝试这样做将引发RuntimeError。这个想法是,一个Task代表一个正在运行的协程,所以结果应该总是来自于task自身。
(L10, L24)但是,我们仍然可以cancel()一个任务,它将在底层协程中引发CancelledError。
Create_task? Ensure_Future? 下定决心吧!
在第22页的“快速入门”中,我说过运行协程的方法是使用asyncio.create_task()。在引入该函数之前,有必要获取一个循环实例并使用loop.create_task()完成相同的任务。事实上,这也可以通过一个不同的模块级函数来实现:asyncio.ensure_future()。一些开发人员推荐create_task(),而其他人推荐ensure_future()。
在我为这本书做研究的过程中,我确信API方法asyncio.ensure_future()是引起对asyncio库广泛误解的罪魁祸首。API的大部分内容都非常清晰,但在学习过程中还存在一些严重的障碍,这就是其中之一。当你遇到ensure_future()时,你的大脑会非常努力地将其集成到关于asyncio应该如何使用的心理模型中——但很可能会失败!
在Python 3.6 asyncio 文档中,这个现在已经臭名昭著的解释突出了 ensure_future() 的问题:
asyncio.ensure_future(coro_or_future, *, _loop =None)
安排执行一个协程对象:把它包装在future中。返回一个Task对象。如果参数是Future,则直接返回。
什么!? 当我第一次读到这篇文章时,我很困惑。下面希望是对ensure_future()的更清楚的描述:
这个函数很好地说明了针对终端用户开发人员的asyncio API(高级API)和针对框架设计人员的asyncio API(低级API)之间的区别。让我们在示例 3-18中自习看看它是如何工作的。
示例 3-18. 仔细看看ensure_future()在做什么
(L3)一个简单的什么都不做的协程函数。我们只需要一些能组成协程的东西。
(L6)我们通过直接调用该函数来创建协程对象。你的代码很少会这样做,但我想在这里明确地表示,我们正在向每个create_task()和ensure_future()传递一个协程对象。
(L7)获取一个循环。
(L9)首先,我们使用loop.create_task()在循环中调度协程,并返回一个新的Task实例。
(L10)验证类型。到目前为止,没有什么有趣的。
(L12)我们展示了asyncio.ensure_future()可以被用来执行与create_task()相同的动作:我们传入了一个协程,并返回了一个Task实例(并且协程已经被安排在循环中运行)!如果传入的是协程,那么loop.create_task()和asyncio.ensure_future()之间没有区别。
(L15)如果我们给ensure_future()传递一个Task实例会发生什么呢?注意我们要传递的Task实例是已经在第4步通过loop.create_task()创建好的。
(L16)返回的Task实例与传入的Task实例完全相同:它在被传递时没有被改变。
直接传递Future实例的意义何在?为什么用同一个函数做两件不同的事情?答案是,ensure_future()的目的是让框架作者向最终用户开发者提供可以处理两种参数的API。不相信我?这是ex-BDFL自己说的:
ensure_future()的要点是,如果你有一个可能是协程或Future(后者包括一个Task,因为它是Future的子类)的东西,并且你想能够调用一个只在Future上定义的方法(可能唯一有用的例子是cancel())。当它已经是Future(或Task)时,它什么也不做;当它是协程时,它将它包装在Task中。
如果您知道您有一个协程,并且希望它被调度,那么正确的API是create_task()。唯一应该调用ensure_future()的时候是当你提供一个API(像大多数asyncio自己的API),它接受协程或Future,你需要对它做一些事情,需要你有一个Future。
—Guido van Rossum
总而言之,asyncio.sure_future()是一个为框架设计者准备的辅助函数。这一点最容易通过与一种更常见的函数进行类比来解释,所以我们来做这个解释。如果你有几年的编程经验,你可能已经见过类似于例3-19中的istify()函数的函数。示例 3-19中listify()的函数。
示例 3-19. 一个强制输入列表的工具函数
这个函数试图将参数转换为一个列表,不管输入的是什么。api和框架中经常使用这类函数将输入强制转换为已知类型,这将简化后续代码——在本例中,您知道参数(来自listify()的输出)将始终是一个列表。
如果我将listify()函数重命名为ensure_list(),那么您应该开始看到与asyncio.ensure_future()的类似之处:它总是试图将参数强制转换为Future(或子类)类型。这是一个实用函数,它使框架开发人员(而不是像你我这样的终端用户开发人员)的工作变得更容易。
实际上,asyncio标准库模块本身使用ensure_future()正是出于这个原因。当你下次查看API时,你会发现函数参数被描述为“可等待对象”,很可能内部使用ensure_future()强制转换参数。例如,asyncio.gather()函数就像下面的代码一样:
aws参数表示“可等待对象”,包括协程、task和future。在内部,gather()使用ensure_future()进行类型强制转换:task和future保持不变,而把协程强制转为task。
这里的关键是,作为终端用户应用程序开发人员,应该永远不需要使用asyncio.ensure_future()。它更像是框架设计师的工具。如果你需要在事件循环上调度协程,只需直接使用asyncio.create_task()来完成。
在接下来的几节中,我们将回到语言级别的特性,从异步上下文管理器开始。