① 简述python进程,线程和协程的区别及应用场景
协程多与线程进行比较
1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。
2) 线程进程都是同步机制,而协程则是异步
3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
② 如何用python写一个协程
作者:LittleCoder
链接:https://www.hu.com/question/54483694/answer/139785021
来源:知乎
着作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
yield`和`yield from`的区别
`yield`题主肯定不陌生,而`yield from`是PEP 380中新增的一个特性。
PEP 380的名字是嵌套子迭代器的语法糖(我喜欢这么翻译,原文是:Syntax for Delegating to a Subgenerator)。
既然是语法糖,那么肯定本来是有别的写法的,这里给出本来的写法:
def subgen():
for i in range(3):
yield 'subgen: %s' % i
return 'subgen returned'def gen():
r = yield from subgen()
print('r = "%s"' % r)
yield rdef gen_without_yield_from():
sg = subgen()
try:
while 1:
yield sg.send(None)
except StopIteration as e:
yield e.valueprint('* [gen] get all values')for v in gen_without_yield_from():
print('get value: %s' % v)print('* [gen_without_yield_from] get all values')for v in gen_without_yield_from():
print('get value: %s' % v)
不难看出,`yield`子迭代器是把子迭代器直接传递出去,`yield from`子迭代器是把子迭代器的值一个一个传出去。
虽然实际把子迭代器当做一个对象直接传递出去也没有问题,也有使用场景(生成迭代器的迭代器)。
但在协程中相较于这个令人愉快的语法糖而言,直接传递就显得没有必要且碍事了。
毕竟我希望使用一个子迭代器是把子迭代器中的代码都运行一遍而不是直接就把这个子迭代器传出来让我自己操作。
所以如果你把子迭代器直接传了出去,asyncio就判断你在做一件奇怪的事情并报了错。
那么,回到问题,给出的程序要怎么通过`yield`调用呢?
# 源程序@asyncio.coroutinedef hello():
print("Hello world!")
yield from asyncio.sleep(1)
print("Hello again!")# 使用[email protected] hello():
print("Hello world!")
for v in asyncio.sleep(1):
yield v
print("Hello again!")
协程和迭代器的区别
举个比喻,迭代器和协程就像火药和枪械,利用火药的特性辅助各种其他东西才造出了枪械。
迭代器就最简单的本质而言就是一个可以暂停的程序。
那么就有这样一个合理的联想,我是不是可以节省下所有不必要的例如等待网站响应的等待时间。
就是我把我的请求发过去以后就把这个程序暂停下来,开启别的程序,等到响应来了再叫我回到这个程序。
那么等待网站响应的时间也就完全没有浪费了,比原来傻傻的等着网站响应真是优秀了许多。
这就是协程。
所以,为什么看上去都是`generator`,迭代器不会天生成为协程呢?
因为没有一个知道什么时候应该叫你回到这个程序的人。
这个人就是`event_loop`(消息循环)。
回到问题,协程是否可以脱离`event_loop`(消息循环)调用。
讲道理是不可以的,但合理联想一下是不是一直不停的告诉程序又到你了就行了。
像这样:
@asyncio.coroutinedef gen():
for i in range(3):
yield ifor i in gen():
print(i)print('end')
的确有些协程这样是可以运行的(这些协程为什么要写成协程?)。
但终究你是在不应该告诉程序到你的时候告诉了他这件事情。
所以显然获取数据的话当时数据根本没有传到,`sleep`的话就根本没有了`sleep`的效果。
只是看上去能够运行,实际完全没有用。
asyncio还为此特地加了一个断言,如果你这样调用`asyncio.sleep`,asyncio会发现你在伪装消息循环骗他。
协程的原理
这是另一个看上去能够运行,实际上完全没有用的事情。
这虽然不是你想问的问题,但你已经碰到了也迟早会意识到,所以一并讲了。
这个问题应该是这样的:为什么我写出来的协程完全没有协程的效果?
import time, [email protected] sleep(symbol, i):
time.sleep(i)
print('[%s] finished')loop = asyncio.get_event_loop()tasks = [sleep('A', 2), sleep('B', 2)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
看到这里你起码可以简单的讲出来,因为显然我们在傻傻的等。
我们没有在开始等待的时候把程序暂停下来,然后在等待结束后继续运行程序,我们一心一意的在等。
我们真的`time.sleep`了两秒,而不是去做了两秒其他的事情。
你有各种选择,可以花式等待。我这里给你两个最基本的例子:
* get请求
* 同步变为协程(线程池)
get请求
为了让你更好的了解asyncio,我从最底层的socket开始写一个协程的get请求给你。
为了模拟延时很大的网站,我在本地开了一个延时服务器,这是服务器程序。
import tornado.ioloopimport tornado.webfrom tornado.gen import coroutine, sleepclass MainHandler(tornado.web.RequestHandler):
@coroutine
def get(self, waitTime=3):
yield sleep(int(waitTime))
self.write('you have waited for %ss' % waitTime)if __name__ == "__main__":
application = tornado.web.Application([
('/([0-9])', MainHandler),
], debug=True)
application.listen(5000)
try:
tornado.ioloop.IOLoop.current().start()
except:
tornado.ioloop.IOLoop.current().stop()
记得打开了这个服务器再运行下面的程序。
import socket, asyncio, timedata = 'GET /%s HTTP/1.1\r\n\r\n'loop = asyncio.get_event_loop()@asyncio.coroutinedef get(i):
future = asyncio.futures.Future(loop=loop)
s = socket.socket()
s.connect(('127.0.0.1', 5000))
s.sendall((data % i).encode('utf8'))
s.setblocking(False)
def callback(future):
future.set_result(s.recv(999).split(b'\r\n\r\n')[-1])
loop.add_reader(s.fileno(), callback, future)
r = yield from future
print('Return value: %s' % r)tasks = [get(3), get(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
同步变为协程(线程池)
这里拿sleep模拟耗时的程序,原理就是开了5个新的线程处理耗时程序。
当然实际的`asyncio.sleep`只需要告诉消息循环一定时间后叫醒我就好了。
import asyncio, sleep, [email protected] sleep(i):
executor = concurrent.futures.ThreadPoolExecutor(5)
future = asyncio.futures.wrap_future(executor.submit(time.sleep, i), loop=loop)
yield from future
print('Slept for %s seconds' % i)tasks = [sleep(3), sleep(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
③ Python 进程,线程,协程,锁机制,你知多少
1.线程和进程:
线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。
2.线程、进程与协程:
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保持状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景: 当程序中存在大量不需要CPU的操作时(IO),适用于协程;
④ python的协程是什么和多线程有啥区别吗
v 先简要说下结论: 协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈,自己的局部变量,有自己的指令指针(IP,instruction pointer),但与其它协同程序共享全局变量等很多信息。 协程(协同程序): 同一时间只能执行某个协程...
⑤ 简述python进程,线程和协程的区别
协程多与线程进行比较
1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。
2) 线程进程都是同步机制,而协程则是异步
3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
⑥ Python 3.5 协程究竟是个啥
用 async def 可以定义得到 协程 。定义协程的另一种方式是通过 types.coroutine 修饰器 -- 从技术实现的角度来说就是添加了 CO_ITERABLE_COROUTINE 标记 -- 或者是 collections.abc.Coroutine 的子类。你只能通过基于生成器的定义来实现协程的暂停。
awaitable 对象 要么是一个协程要么是一个定义了 __await__() 方法的对象 -- 也就是 collections.abc.Awaitable -- 且 __await__() 必须返回一个不是协程的迭代器。 await 表达式基本上与 yield from 相同但只能接受awaitable对象(普通迭代器不行)。 async 定义的函数要么包含 return 语句 -- 包括所有Pytohn函数缺省的 return None -- 和/或者 await 表达式( yield 表达式不行)。 async 函数的限制确保你不会将基于生成器的协程与普通的生成器混合使用,因为对这两种生成器的期望是非常不同的。
⑦ 详解Python中的协程,为什么说它的底层是生成器
协程又称为是微线程,英文名是Coroutine。它和线程一样可以调度,但是不同的是线程的启动和调度需要通过操作系统来处理。并且线程的启动和销毁需要涉及一些操作系统的变量申请和销毁处理,需要的时间比较长。而协程呢,它的调度和销毁都是程序自己来控制的,因此它更加轻量级也更加灵活。
协程有这么多优点,自然也会有一些缺点,其中最大的缺点就是需要编程语言自己支持,否则的话需要开发者自己通过一些方法来实现协程。对于大部分语言来说,都不支持这一机制。go语言由于天然支持协程,并且支持得非常好,使得它广受好评,短短几年时间就迅速流行起来。
对于Python来说,本身就有着一个GIL这个巨大的先天问题。GIL是Python的全局锁,在它的限制下一个Python进程同一时间只能同时执行一个线程,即使是在多核心的机器当中。这就大大影响了Python的性能,尤其是在CPU密集型的工作上。所以为了提升Python的性能,很多开发者想出了使用多进程+协程的方式。一开始是开发者自行实现的,后来在Python3.4的版本当中,官方也收入了这个功能,因此目前可以光明正大地说,Python是支持协程的语言了。
生成器(generator)
生成器我们也在之前的文章当中介绍过,为什么我们介绍协程需要用到生成器呢,是因为Python的协程底层就是通过生成器来实现的。
通过生成器来实现协程的原因也很简单,我们都知道协程需要切换挂起,而生成器当中有一个yield关键字,刚好可以实现这个功能。所以当初那些自己在Python当中开发协程功能的程序员都是通过生成器来实现的,我们想要理解Python当中协程的运用,就必须从最原始的生成器开始。
生成器我们很熟悉了,本质上就是带有yield这个关键词的函数。
async,await和future
从Python3.5版本开始,引入了async,await和future。我们来简单说说它们各自的用途,其中async其实就是@asyncio.coroutine,用途是完全一样的。同样await代替的是yield from,意为等待另外一个协程结束。
我们用这两个一改,上面的代码就成了:
async def test(k):
n = 0
while n < k:
await asyncio.sleep(0.5)
print('n = {}'.format(n))
n += 1
由于我们加上了await,所以每次在打印之前都会等待0.5秒。我们把await换成yield from也是一样的,只不过用await更加直观也更加贴合协程的含义。
Future其实可以看成是一个信号量,我们创建一个全局的future,当一个协程执行完成之后,将结果存入这个future当中。其他的协程可以await future来实现阻塞。我们来看一个例子就明白了:
future = asyncio.Future()
async def test(k):
n = 0
while n < k:
await asyncio.sleep(0.5)
print('n = {}'.format(n))
n += 1
future.set_result('success')
async def log():
result = await future
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
log(),
test(5)
]))
loop.close()
在这个例子当中我们创建了两个协程,第一个协程是每隔0.5秒print一个数字,在print完成之后把success写入到future当中。第二个协程就是等待future当中的数据,之后print出来。
在loop当中我们要调度执行的不再是一个协程对象了而是两个,所以我们用asyncio当中的wait将这两个对象包起来。只有当wait当中的两个对象执行结束,wait才会结束。loop等待的是wait的结束,而wait等待的是传入其中的协程的结束,这就形成了一个依赖循环,等价于这两个协程对象结束,loop才会结束。
总结
async并不只是可以用在函数上,事实上还有很多其他的用法,比如用在with语句上,用在for循环上等等。这些用法比较小众,细节也很多,就不一一展开了,大家感兴趣的可以自行去了解一下。
不知道大家在读这篇文章的过程当中有没有觉得有些费劲,如果有的话,其实是很正常的。原因也很简单,因为Python原生是不支持协程这个概念的,所以在一开始设计的时候也没有做这方面的准备,是后来觉得有必要才加入的。那么作为后面加入的内容,必然会对原先的很多内容产生影响,尤其是协程借助了之前生成器的概念来实现的,那么必然会有很多耦合不清楚的情况。这也是这一块的语法很乱,对初学者不友好的原因。
⑧ 在python中线程和协程的区别是什么
在python中线程和协程的区别:1、一个线程可以拥有多个协程,这样在python中就能使用多核CPU;2、线程是同步机制,而协程是异步;3、 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
一、首先我们来了解一下线程和协程的概念
1、线程
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
2、协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
二、协程与线程的比较
1) 一个线程可以拥有多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。
2) 线程进程都是同步机制,而协程则是异步。
3)协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
三、线程、协程在python中的使用
1、多线程一般是使用threading库,完成一些IO密集型并发操作。多线程的优势是切换快,资源消耗低,但一个线程挂掉则会影响到所有线程,所以不够稳定。现实中使用线程池的场景会比较多,具体可参考《python线程池实现》。
2、协程一般是使用gevent库,当然这个库用起来比较麻烦,所以使用的并不是很多。相反,协程在tornado的运用就多得多了,使用协程让tornado做到单线程异步,据说还能解决C10K的问题。所以协程使用的地方最多的是在web应用上。
总结一下:
IO密集型一般使用多线程或者多进程,CPU密集型一般使用多进程,强调非阻塞异步并发的一般都是使用协程,当然有时候也是需要多进程线程池结合的,或者是其他组合方式。
推荐课程:Python高级进阶视频教程