协程

协程(Coroutine),又称微线程,纤程。协程是一种用户态的轻量级线程。

协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其它地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上次调用的状态,换种说法:进入上次离开时所处逻辑流的位置。

 

协程的优点:

无需线程上下文切换的开销(因为是单线程)

无需原子操作(不会被线程调度机制打断的操作,如修改一个变量)锁定及同步的开销

方便切换控制流,简化编程模型

高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题,所以很适用于高并发处理。

 

缺点:

无法利用多核资源:协程的本质是单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上,当然我们日常所编写的绝大部分应用都没有这个必要,除非是CPU密集型应用。

进行阻塞操作会阻塞掉整个程序。

 

消费者模型模拟协程

# coding=utf-8


def producer():
    c1.next()  # py3用__next__方法
    c2.next()
    n = 1
    while n < 6:
        print 'producer is making food {}'.format(n)
        c1.send(n)  # send表示唤醒生成器的同时发送一个值
        c2.send(n)
        n += 1


def consumer(name):
    print 'Start eating'
    while True:
        food = yield  # 遇到yield的时候停止,被唤醒的时候传值给food
        print '{} eating {}'.format(name, food)


c1 = consumer('c1')
c2 = consumer('c2')
p = producer()

结果:
Start eating
Start eating
producer is making food 1
c1 eating 1
c2 eating 1
producer is making food 2
c1 eating 2
c2 eating 2
producer is making food 3
c1 eating 3
c2 eating 3
producer is making food 4
c1 eating 4
c2 eating 4
producer is making food 5
c1 eating 5
c2 eating 5

 

Nginx就是利用的协程实现的高并发,它默认是单线程,遇到IO操作时就切走执行其它CPU操作,IO操作完毕时再切回。

 

greenlet实现简单协程中的切换

# coding=utf-8
from greenlet import greenlet


def f1():
    print 12
    gb.switch()
    print 56


def f2():
    print 34
    ga.switch()
    print 78


ga = greenlet(f1)
gb = greenlet(f2)
ga.switch()

结果:
12
34
56

 

模拟协程中的切换

# coding=utf-8
import gevent


def f1():
    print 12
    gevent.sleep(2)
    print 1112


def f2():
    print 34
    gevent.sleep(1)
    print 78

def f3():
    print 56
    gevent.sleep(1)
    print 910


gevent.joinall([
    gevent.spawn(f1),
    gevent.spawn(f2),
    gevent.spawn(f3),
])

输出:
12
34
56
78
910
1112

 

模拟实际应用

在本地用一个Django的视图函数模拟实际IO处理

urls.py

from django.conf.urls import include, url
from django.contrib import admin
from main.views import *

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^mysleep/(?P<second>\d+)/', mysleep, name='mysleep'),
]

views.py

from django.http import JsonResponse
import time

# Create your views here.

def mysleep(request, second):
    time.sleep(int(second))
    return JsonResponse({'status': 'ok'})

 

# 常规的串行效果

# coding=utf-8
import gevent
import requests
import time


def f(second):
    r = requests.get('http://127.0.0.1:8000/mysleep/{}/'.format(second))


if __name__ == "__main__":
    time_start = time.time()
    f(1)
    f(2)
    print u'串行总计:{}秒'.format(time.time() - time_start)


输出:
串行总计:3.09899997711秒

# 用协程实现并行

# coding=utf-8
import gevent
import requests
import time
from gevent import monkey

monkey.patch_all()  # 把当前程序的IO操作做标记


def f(second):
    r = requests.get('http://127.0.0.1:8000/mysleep/{}/'.format(second))


if __name__ == "__main__":
    time_start = time.time()
    gevent.joinall([
        gevent.spawn(f, 1),
        gevent.spawn(f, 2)
    ])
    print u'并行总计:{}秒'.format(time.time() - time_start)

输出:
并行总计:2.03000020981秒

 

posted @ 2019-02-25 22:08  AllenZhang_(*^▽^*)  阅读(179)  评论(0编辑  收藏  举报