python之协程

 

  一、这几天在折腾Python的协程问题,Python的协程相对原理上来说要简单很多了。在使用Java做开发的时候,经常使用线程,过程中经常也有听说过微线程/协程的概念,但是没有去深刻的学习过Java的协程是怎么实现的。这一篇文章主要是讲述Python中的协程原理。后面补上Java中的协程原理!

  二、前面我们基本大概了解了多线程和多进程的问题,在资源的占用上面,多进程比多线程占用的多。我们这这里通过cpu调度最小的单位线程来说:

  1)多线程:多线程的调用需要cpu分配时间片然后来回的切换,并且是随机的。

  2)协程:协程是在单线程中的多个任务同时执行的相互配合,避免了线程之间切换造成的资源浪费

  3)意义:当我们在使用多线程的时候,如果存在长时间的I/O操作。这个时候线程一直处于阻塞状态,如果线程很多的时候,会存在很多线程处于空闲状态,造成了资源应用不彻底。相对的协程不一样了,在单线程中多个任务来回自行如果出现长时间的I/O操作,让其让出目前的协程调度,执行下一个任务。当然可能所有任务,全部卡在同一个点上,但是这只是针对于单线程而言,当所有数据正常返回时,会同时处理当前的I/O操作。

  4)作用:用更小的资源做更多地操作,更加高效的利用线程。当然,也有问题点,如果存在计算密集型的操作,协程起到的效果不是那么大。

  5)理解:协程不是真正意义上的像线程一样开辟空间(比如一个线程在64位系统下,stack大小为1024KB),协程没有,只是在单线程中,多个任务来回切换造成的多任务执行的假象。

  三、实现原理

  1)基本原理:是用过yield的方式来实现,来阻塞当前执行,等到下一个next()来了过后接着执行。

  yield:生成器的关键字,通过暂停并返回数据,达到产生数据的效果。

  2)代码理解:

import time

def show_a():
    while True:
        print "---A---"
        yield
        time.sleep(0.5)

def show_b(c):
    while True:
        print "---B---"
        c.next()
        time.sleep(0.5)

if __name__ == '__main__':
    # 生成器
    a = show_a()
    # 调用方法show_b
    show_b(a)

  3)测试结果:

  

  4)解释:

  a、协程是通过生成器的方式来达到效果的。

  b、目的让多个任务来回/同时执行

  四、通过greenlet实现协程

from greenlet import greenlet

def test1():
    while True:
        print "----A----"
        # 切换
        gr2.switch()

def test2():
    while True:
        print "----B----"
        # 切换
        gr1.switch()

gr1 = greenlet(test1)
gr2 = greenlet(test2)

if __name__ == '__main__':
    gr1.switch()

  效果:

  

  这样达到的效果就是多任务执行

  解释:

  这里的switch()方法就是yield的效果

  五、通过gevent达到协程的效果,这里通过socket连接阻塞的例子来演示:

from gevent import socket, monkey

# 用于动态加入yield
monkey.patch_all()

def request_handle(cli, address):
    while True:
        # 等待接收数据,阻塞
        data = cli.recv(1024)
        if len(data) > 0:
            print data
        else:
            print "close" + str(address)
            cli.close
            break

def server(port):
    s = socket.socket()
    s.bind(('', port))
    s.listen(5)
    while True:
        # 等待接入,阻塞
        cli, address = s.accept()
        print address
        # 新建一个协程
        gevent.spawn(request_handle, cli, address)

def server_test():
    while True:
        print "test"
        gevent.sleep(1)

if __name__ == '__main__':
    # 为了独立运行,加入2个协程任务
    s1 = gevent.spawn(server, 9000)
    # 用于展示独立性
    s2 = gevent.spawn(server_test)
    s1.join()
    s2.join()

  效果:

  

  注意:这里的yield是自动加入的,目的是自动让出时间执行过长的任务,到可以执行的任务中区执行。

  阻塞为一种长时间的等待,当有新的连接进来,才会执行请求任务。

  这里客户端使用telnet测试。

  六、总结:Python的协程原理是通过yield关键字,并且通过生成器的方式来达到多任务的执行。对于I/O密集型的操作,可以采用协程方式来做。如果存在计算密集型(占用cpu的操作很高),不要使用协程,最好使用多进程。

个人对于协程的理解,基本上就是这些,如果存在错误的地方还请指出。

 

posted @ 2019-05-29 16:12  小不点丶  阅读(1284)  评论(0编辑  收藏  举报