python 协程

协程

  • 单线程下实现并发

  • 是一种轻量级的线程, 由程序自己控制调度的 线程是内核级的, 协程是程序级别的

  • 优点:

    协程的切换开销小,操作系统完全感知不到, 单线程内就可以实现并发效果,最大限度的 利用 CPU

  • 协程遇到 IO 就自动切换到其他协程 检测 IO yield greenlet 无法实现

    用 gevent 模块(select 机制) 实现 IO 的切换

  • 并发:

    伪并行,遇到IO就切换,单核下多个任务之间切换执行,给你的效果就是貌似你的几个程序在同时执行.提高效率
    任务切换 + 保存状态

  • 并行:

    多核cpu,真正的同时执行

  • 串行:

    一个任务执行完在执行另外一个任务

  • 首先,给出“进程、线程和协程”的特点:

    进程:拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;
    线程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,标准线程由操作系统调度;
    协程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

    import time
    '''串行 用时'''
    def func1():
        time.sleep(1)
        c = 0 
        for i in range(1000):
            c = i ** i
        print("func1>>", c)
    
    def func2():
        time.sleep(2)
        c = 0
        for i in range(1000):
            c = i ** i
        print("func2>>", c)
    
    
    if __name__ == '__main__':
        s_t = time.time()
        func1()
        func2()
        print(time.time() - s_t)  # 3.0608773231506348
    

    基于yield并发执行

    '''基于yield并发执行,多任务之间来回切换,这就是个简单的协程的体现,但是他能够节省I/O时间吗?不能'''
    
    def func1():
        while 1:
            time.sleep(1)
            x = yield
            print("接收了%s 个任务" % x)
    
    def func2():
        f = func1()
        # 找到 第一个 yield
        next(f)
        for i in range(1, 11):
            f.send(i)
            print("发送了 %s 个任务" % i)
    
    s_t = time.time()
    # 基于yield保存状态,实现两个任务直接来回切换,即并发的效果
    # PS: 如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的.
    
    func2()  # 我在当前线程中只执行了这个函数,但是通过这个函数里面的send切换了另外一个任务
    print(time.time() - s_t)  # 11.003206253051758
    
    
    # 串行的执行 方式
    # func1()
    # func2()
    # print(time.time() - s_t)  # 11.00475525856018
    

greenlet 模块

  • 真正的协程模块就是使用 greenlet 完成的切换

import time
from greenlet import greenlet

def eat(name):
    print('%s eat 1' % name)  # 2
    time.sleep(3)
    g2.switch('taibai')  # 3
    print('%s eat 2' % name)  # 6
    g2.switch()  # 7


def play(name):
    print('%s play 1' % name)  # 4
    time.sleep(3)
    g1.switch()  # 5
    print('%s play 2' % name)  # 8

g1 = greenlet(eat)
g2 = greenlet(play)

g1.switch('taibai')  # 可以在第一次switch时传入参数,以后都不需要

gevent 使用

  • 作用: 任务切换 + 保存状态 实现了 IO 自动切换
import gevent
from gevent import monkey
monkey.patch_all()   # 补丁 识别所有的 IO 阻塞   time.sleep()  就可以用了
import time

def func1(name):
    print(name, 111111111)
    time.sleep(1)
    # gevent.sleep(1)
    print(name, 2222222222)
    return 123


def func2(name):
    print(name, 111)
    # gevent.sleep(1)  # time.sleep() 识别不到
    time.sleep(1)
    print(name, 222)


g1 = gevent.spawn(func1, "egen")  # 异步执行这个func1任务,后面egon就是给他传的参数
print(g1.value)  # 取出返回值的 结果
g2 = gevent.spawn(func2, "two")

# 此 方法 不用 单独 指定 join()
gevent.joinall([g1, g2])
print("aaaaaa")
################################################################

def task(pid):
    """
    Some non-deterministic task
    """
    time.sleep(0.5)
    print('Task %s done' % pid)


def synchronous():
    for i in range(10):
        task(i)

def asynchronous():
    g_l = [gevent.spawn(task, i) for i in range(10)]
    gevent.joinall(g_l)


if __name__ == '__main__':
    print('Synchronous:')  # 同步的 并发 执行
    synchronous()
    print('Asynchronous:')  # 异步的 并行 执行
    asynchronous()
# 上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。

posted @ 2019-04-23 13:18  拐弯  阅读(161)  评论(0编辑  收藏  举报