博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
进程、线程、协程
阅读量:4608 次
发布时间:2019-06-09

本文共 2643 字,大约阅读时间需要 8 分钟。

 进程、线程和协程的理解

、和之间的关系和区别也困扰我一阵子了,最近有一些心得,写一下。

进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。

协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

进程和其他两个的区别还是很明显的。

协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

打个比方吧,假设有一个操作系统,是单核的,系统上没有其他的程序需要运行,有两个线程 A 和 B ,A 和 B 在单独运行时都需要 10 秒来完成自己的任务,而且任务都是运算操作,A B 之间也没有竞争和共享数据的问题。现在 A B 两个线程并行,操作系统会不停的在 A B 两个线程之间切换,达到一种伪并行的效果,假设切换的频率是每秒一次,切换的成本是 0.1 秒(主要是栈切换),总共需要 20 + 19 * 0.1 = 21.9 秒。如果使用协程的方式,可以先运行协程 A ,A 结束的时候让位给协程 B ,只发生一次切换,总时间是 20 + 1 * 0.1 = 20.1 秒。如果系统是双核的,而且线程是标准线程,那么 A B 两个线程就可以真并行,总时间只需要 10 秒,而协程的方案仍然需要 20.1 秒。

一个实际一点的例子:

#!/usr/bin/python    # python thread.py    # python -m gevent.monkey thread.py    import threading    class Thread(threading.Thread):        def __init__(self, name):            threading.Thread.__init__(self)            self.name = name        def run(self):            for i in xrange(10):                print self.name    threadA = Thread("A")    threadB = Thread("B")    threadA.start()    threadB.start()

 

运行:

python thread.py

如果你的输出是均匀的:

ABAB...

那么总共发生了 20 次切换:主线程 -> A -> B -> A -> B …

再看一个协程的例子:

#!/usr/bin/python    # python gr.py    import greenlet    def run(name, nextGreenlets):        for i in xrange(10):            print name        if nextGreenlets:            nextGreenlets.pop(0).switch(chr(ord(name) + 1), nextGreenlets)    greenletA = greenlet.greenlet(run)    greenletB = greenlet.greenlet(run)    greenletA.switch('A', [greenletB])

 

 是 的协程实现。

运行:

python gr.py

此时发生了 2 次切换:主协程 -> A -> B

可能你已经注意到了,还有一个命令:

python -m gevent.monkey thread.py

 是 基于 greenlet 的一个 python 库,它可以把 python 的内置线程用 greenlet 包装,这样在我们使用线程的时候,实际上使用的是协程,在上一个协程的例子里,协程 A 结束时,由协程 A 让位给协程 B ,而在 gevent 里,所有需要让位的协程都让位给主协程,由主协程决定运行哪一个协程,gevent 也会包装一些可能需要阻塞的方法,比如 sleep ,比如读 socket ,比如等待锁,等等,在这些方法里会自动让位给主协程,而不是由程序员显示让位,这样程序员就可以按照线程的模式进行线性编程,不需要考虑切换的逻辑。

gevent 版的命令发生了 3 次切换:主协程 -> A -> 主协程 -> B

假设代码质量相同,用原生的协程实现需要切换 n 次,用协程包装后的线程实现,就需要 2n - 1 次,姑且算是两倍吧。很显然,单纯从效率上来说,代码质量相同的前提下,用 gevent 永远也不可能比用 greenlet 快,然而,问题往往不那么单纯,比方说,单纯从效率上来说,代码质量相同的前提下,用 C 实现的程序永远不可能比汇编快。

再来说说 python 的线程,python 的线程不是标准线程,在 python 中,一个进程内的多个线程只能使用一个 CPU 。

重新来看一下协程和线程的区别:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

如果使用 gevent 包装后的线程,程序员就不必承担调度的责任,而 python 的线程本身就没有使用多 CPU 的能力,那么,用 gevent 包装后的线程,取代 python 的内置线程,不是只有避免无意义的调度,提高性能的好处,而没有什么坏处了吗?

答案是否定的。举一个例子,有一个 GUI 程序,上面有两个按钮,一个 运算 一个 取消 ,点击运算,会有一个 运算线程启动,不停的运算,点击取消,会取消这个线程,如果使用 python 的内置线程或者标准线程,都是没有问题的,即便运算线程不停的运算,调度器仍然会给 GUI 线程分配时间片,用户可以点击取消,然而,如果使用 gevent 包装后的线程就完蛋了,一旦运算开始,GUI 就会失去相应,因为那个运算线程(协程)霸着 CPU 不让位。不单是 GUI ,所有和用户交互的程序都会有这个问题。

转载于:https://www.cnblogs.com/work115/p/5607421.html

你可能感兴趣的文章
Windows系统maven安装配置
查看>>
k8s-job使用
查看>>
myeclipse codelive插件关闭
查看>>
curl操作和file_get_contents() 比较
查看>>
jq库extend的区别
查看>>
CSS3 一些很实用的属性
查看>>
替换空格
查看>>
gif图再html网页中只播放一次
查看>>
Zipline入门教程
查看>>
GoLand Dep Integration Project
查看>>
第二十一节,使用TensorFlow实现LSTM和GRU网络
查看>>
windows10下安装apache2.4并运行
查看>>
kernel: INFO: task sadc:14833 blocked for more than 120 seconds.
查看>>
【刷题】BZOJ 3998 [TJOI2015]弦论
查看>>
巧用FPGA中资源
查看>>
网络流24题之飞行员配对方案问题
查看>>
spring mvc 之@requestmapping
查看>>
linux vi编辑器
查看>>
js树形结构-----(BST)二叉树增删查
查看>>
contract
查看>>