七叶笔记 » golang编程 » Python干货整理:python与golang的协程区别

Python干货整理:python与golang的协程区别

什么是协程?

协程 算是一种特殊的程序调用。 它有两个特征:

  1. 可中断:是类似CPU的中断,CPU在这里直接释放转到其他程序断点继续执行。
  2. 可恢复:等到合适的时候,可以恢复到中断的地方继续执行

特殊之处 :在执行过程中,在子程序(或者说函数)内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

和进程线程的区别

无论是进程还是线程,都是由操作系统所管理和切换的。

进程和线程 的切换完全是用户无感,从用户态到内核态再到用户态。

协程 的切换完全是程序代码控制的,在用户态的切换,就像函数回调的消耗一样,在线程的栈内完成。

进程是操作系统资源分配的基本单位。

进程是程序的启动实例,拥有代码和打开的文件资源、数据资源、独立的内存空间。

线程是操作系统调度和执行的最小单位

线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。

协程又叫微线程,但其实它和进程还有线程完全不是一个维度上的概念

python的协程(Goroutine)

协程的概念

python的协程其实是我们通常意义上的协程Goroutine,在适当的时候可中断可恢复。

还可以理解为**生成器+调度策略**,生成器中的**yield**关键字,就可以让生成器函数发生中断,而调度策略,可以驱动着协程的执行和恢复。

调度策略对协程做了更好的封装 ,方法如下:

  • 忙轮循:while True
  • for循环
  • 基于epool的事件循环
  • python2的tornado
  • python3的asyncio中

通过yield和await使用协程,通过事件循环监控文件描述符状态来驱动协程恢复执行。

我们看一个简单的协程:

 import time

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        time.sleep(1)
        r = '200 OK'
def produce(c):
    c.next()
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

if __name__=='__main__':
    c = consumer()
    produce(c)  

asyncio支持的基于epool的事件循环:

 def main():
    define_options()
    options.parse_command_line()
    # 使用uvloop代替原生事件循环
    # asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
    app = tornado.web.Application(handlers=handlers, debug=options.debug)
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    asyncio.get_event_loop().run_forever()  

async/await支持的原生协程:

 class RcOutputHandler(BaseHandler):
    async def post(self):
        status, msg, user = self.check_args('uid', 'order_no', 'mid', 'phone', 'name', 'apply_id',
                                            'product_id')
        if status != ErrorCodeConfig.SUCCESS:
            status, msg, report = status, msg, None
        else:
            rcoutput_flow_instance = ZHANRONG_CUSTOM_PRODUCTID_RCFLOW_MAP.get(user.product_id,
                                                                              RcOutputFlowControler())
            status, msg, report = await rcoutput_flow_instance.get_rcoutput_result(user)
        res = self.generate_response_data(status, msg, report)
        await self.finish(res)
        # 陪跑流程
        await AccompanyRunningFlowControler().get_accompany_data(user)  

总结一下python协程的特点:

  • 单线程内切换,适用于IO密集型程序中,可以最大化IO多路复用的效果。
  • 无法利用多核。
  • 协程间完全同步,不会并行。不需要考虑数据安全。
  • 用法多样,可以用在web服务中,也可用在pipeline数据/任务消费中

golang的协程(goroutine)

golang的协程兼具协程和线程的优势,从语言层面支持并发,同样是在适当的时候可中断可恢复。

Go语言里,go function很容易启动一个goroutine,goroutine可以在多核上运行,从而实现协程并行

当协程中发生channel读写的阻塞或者系统调用时,就会切换到其他协程。

如上图, M 指的是 Machine ,一个 M 直接关联了一个内核线程。由操作系统管理。
P 指的是”processor”,代表了 M 所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文,将等待执行的G与M对接。
G 指的是 Goroutine ,其实本质上也是一种轻量级的线程。包括了调用栈,重要的调度信息,例如channel等。

每次go调用的时候,都会:

  1. 创建一个G对象,加入到本地队列或者全局队列
  2. 如果还有空闲的P,则创建一个M
  3. M会启动一个底层线程,循环执行能找到的G任务
  4. G任务的执行顺序是,先从本地队列找,本地没有则从全局队列找(一次性转移(全局G个数/P个数)个,再去其它P中找(一次性转移一半),
  5. 以上的G任务执行是按照队列顺序(也就是go调用的顺序)执行的。

对于上面的第2-3步,创建一个M,其过程:

  1. 先找到一个空闲的P,如果没有则直接返回
  2. 调用系统api创建线程,不同的操作系统,调用不一样,其实就是和c语言创建过程是一致的
  3. 然后创建的这个线程里面才是真正做事的,循环执行G任务

当协程发生阻塞切换时:

  1. M0出让P
  2. 创建M1接管P及其任务队列继续执行其他G。
  3. 当阻塞结束后,M0会尝试获取空闲的P,失败的话,就把当前的G放到全局队列的队尾。

这里我们需要注意三点:
1、M与P的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来。

2、P何时创建:在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P。

3、M何时创建:没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M。

总结一下go协程的特点:

  • 协程间需要保证数据安全,比如通过channel或锁。
  • 可以利用多核并行执行。
  • 协程间不完全同步,可以并行运行,具体要看channel的设计。
  • 抢占式调度,可能无法实现公平。

coroutine(python)和goroutine(go)的协程区别

共同点 :最大的特色和优势就是都支持协程。

执行机制 上面说:goroutine 可能在多核上发生并行执行,适用 IO密集和CPU密集中。coroutine 始终是顺序执行,适用于IO密集程序中,单线程内的协程切换效率更高,IO并发密集的程序中;

运行机制 上来说,coroutine 的运行机制属于协作式任务处理,goroutine 属于抢占式任务处理,

Python协程模式:多个协程在一个线程中切换。在IO密集时切换效率高,但没有用到多核

go模式:多个协程在多个线程上切换,既可以用到多核,又可以减少切换开销。

(当都是cpu密集时,在多核上切换好,当都是io密集时,在单核上切换好)。

协程通信和调度机制 来看:

想要了解更多Python知识,如果你是想要学习Python或正在学习python的小伙伴,作者给大家提供了一个互相交流学习的平台,点击下方【了解更多】,与志同道合的小伙伴一起学习吧~

相关文章