协程

#############################  协 程  #######################################
进程 : 资源分配的最小单位 ,班级
线程: cpu调度最小单位 , 人
什么是协程? 一个人分八半
协程 :能在一条线程的基础上,在多个任务之间互相切换
节省了线程的开启的消耗
是从python代码的级别调度的
正常的线程是CPU调度的最小单位
协程的调度并不是由操作系统来完成的
在两个任务之间互相切换-协程
def func():
    print(1)
    x = yield 'aaa'
    print(x)
    yield 'bbb'

g = func()
print(next(g))       ##接收的返回值
print(g.send(****))    ##传给了x

#得到结果:
                1
                aaa
                ****
                bbb


在多个函数之间互相切换的功能  -协程
def consumer():
        while True:
            x = yield
            print(x)
def producer():
        g = consumer()
        next(g)       #预激
        for i in range(10):
                g.send(i)
producer()

yield 只有程序之间的切换,没有重利用任何IO操作的时间 
切换

需要强调的是:
#1.python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会*交出cpu执行权限,切换其他线程运行)
#2.单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

对比操作系统控制线程的切换,用户在单线程内控制协程的切换

优点:
#1,协程的切换开销更小, 属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
#2,单线程内就可以实现并发的效果,最大限度的利用cpu

缺点:
#1,协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
#2,协程指的是单个线程,因而一旦协程出现阻塞,将会则色整个线程

总结协程特点:

  1.必须在只有一个单线程里实现并发

  2.修改共享数据不需加锁

  3.用户程序里自己保存多个控制流的上下文栈

  4.附加:一个协程遇到IO操作自动切换到其他协程(如何实现检测IO,yield,greenlet都无法实现,就用到了gevent模块(select机制))

Greenlet模块

安装 pip3 install greenlet

import time
from greenlet import greenlet
# 协程模块  # 单纯的程序切换耗费时间
def eat():
    print('')
    time.sleep(1)
    g2.switch()  # 切换
    print('吃完了')
    time.sleep(1)
    g2.switch()

def play():
    print('玩儿')
    time.sleep(1)
    g1.switch()
    print('玩儿美了')
    time.sleep(1)

g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch()   # 切换
greenlet 切换

单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

#顺序执行
import time
def f1():
    res=1
    for i in range(100000000):
        res+=i

def f2():
    res=1
    for i in range(100000000):
        res*=i

start=time.time()
f1()
f2()
stop=time.time()
print('run time is %s' %(stop-start)) #10.985628366470337

#切换
from greenlet import greenlet
import time
def f1():
    res=1
    for i in range(100000000):
        res+=i
        g2.switch()

def f2():
    res=1
    for i in range(100000000):
        res*=i
        g1.switch()

start=time.time()
g1=greenlet(f1)
g2=greenlet(f2)
g1.switch()
stop=time.time()
print('run time is %s' %(stop-start)) # 52.763017892837524
效率对比

greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题

单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块

Gevent模块

安装;  pip3 install gevent

g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的

g2=gevent.spawn(func2)

g1.join() #等待g1结束

g2.join() #等待g2结束

#或者上述两步合作一步:gevent.joinall([g1,g2])

g1.value#拿到func1的返回值
用法介绍
#使用协程减少IO操作带来的时间消耗
from gevent import monkey;monkey.patch_all()
import gevent
import time

def eat():
    print('')
    time.sleep(2)
    print('吃完了')

def play():
    print('玩儿')
    time.sleep(1)
    print('玩儿美了')

g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
gevent.joinall([g1,g2])      ##相当于 g1.join()  和g2.join()的结合       

#要加join的  不加join的话 不会有结果

#gevent帮你做了切换,做切换是由条件的,遇到IO才切换
#gevent 不认识除了gevent这个模块内意外的IO操作
#使用join可以一直阻塞直到协程任务完成

#帮助gevent来认识其他模块中的阻塞,就需要把  
# from gevent import monkey;monkey.path_all() 写在其他模块导入之前
例子

上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前

或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

协程完成的socket

from gevent import monkey;monkey.patch_all()
import socket
import gevent

def talk(conn):
    while True:
        conn.send(b'hello')
        print(conn.recv(1024))

sk = socket.socket()
sk.bind(('127.0.0.1',9527))
sk.listen()

while True:
    conn,addr = sk.accept()
    gevent.spawn(talk,conn)
server 服务器
import socket
from threading import Thread
def client():
    sk = socket.socket()
    sk.connect(('127.0.0.1',9527))
    while True:
        print(sk.recv(1024))
        sk.send(b'bye')

for i in range(500):
    Thread(target = client).start()
client 客户端

协程:   能够在单核的情况下,极大地提高CPU的利用率

    不存在数据不安全

    也不存在线程切换创造的时间开销

    切换是用户级别的,程序不会因为协程中某一个任务进入阻塞状态而使整条线程阻塞

线程的切换:

    时间片到了, 降低CPU的效率

    IO回切     提高CPU的效率

相关推荐