协程

# Python中的协程和生成器很相似,但是有不同
# 生成器是数据的生产者
# 协程则是数据的消费者
# 协程:让单线程能够同时运行多个任务(实现单线程的并发),是一种用户态的轻量级线程,
# 协程中任务的切换是通过自己的代码是实现切换的(程序级别的切换,切换开销过小,操作系统感知不到)
# 进程或线程中任务的切换是通过系统调度的

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

1.1
def printer():
    counter = 0
    while True:
        string = (yield)
        print('[{0}] {1}'.format(counter, string))
        counter += 1

if __name__ == '__main__':
    p = printer()
    next(p)
    p.send('Hi')
    p.send('My name is hsfzxjy.')
    p.send('Bye!')

结果:
[0] Hi
[1] My name is hsfzxjy.
[2] Bye!

# 不仅快而且不会给内存带来压力,因为我们所需要的值都是动态生成的就像协程中包含的生成器并不是立刻执行,
# 发送的值会被yield接收。使用next()启动一个协程。就像协程中包含的生成器并不是立刻执行而是通过next()方法来响应send()方法。
# 因此,你必须通过next()方法来执行yield表达式。
生成器版的任务切换

1.2 greenlet 提供切换的机制,但是不能解决遇I/O 自动切换,提高效率

 协程中,Greenlet模块的使用
# 多个任务之间切换,使用yield生成器的方式过于麻烦
# (需要先得到初始化一次的生成器,然后再调用send。。。非常麻烦)
# ,而使用greenlet模块可以非常简单地实现这20个任务直接的切换
from greenlet import greenlet
def eat(name):
    print('%s eat 1' %name)  #2
    g2.switch('我是第二个')   #3  使用switch切换到对应的函数的时候,并在第一次对该函数传值,以后都不需要了
    print('%s eat 2' %name) #6
    g2.switch() #7
def play(name):
    print('%s play 1' %name) #4
    g1.switch()      #5
    print('%s play 2' %name) #8

g1=greenlet(eat)#输入函数名
g2=greenlet(play)

g1.switch('我是第一个')#可以在第一次switch时传入参数,以后都不需要  1
# 结果
'''
我是第一个 eat 1
我是第二个 play 1
我是第一个 eat 2
我是第二个 play 2
'''
# # 单纯的切换(在没有I/O的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度
# # greenlet只是提供了一种比生成器提供一种更加便捷的切换方式,当切到一个任务执行时如果遇到I/O操作,
# # 那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
greenlet的使用

1.3 Gevent

# greenlet 只是提供了一种切换的机制(可以是随意切换)
# 而gevent提供的机制 是一个任务中遇到了阻塞才进行切换,利用任务1的阻塞的时间去执行另一个可以执行计算操作的任务,这样就提高了CPU的效率
#
# gevent是第三方库,可以轻松通过gevent实现并发同步或异步编程,以C扩展模块形式接入Python的轻量级协程

# 基本用法

from gevent import monkey;monkey.patch_all()
import gevent,time
def eat(name):
    print('%s eat 1*' %name)
    gevent.sleep(2) #3  使用switch切换到对应的函数的时候,并在第一次对该函数传值,以后都不需要了
    print('%s eat 2**' %name) #
    return 'ha'

def play(name):
    print('%s play 1#' %name)
    gevent.sleep(1)
    print('%s play 2##' %name)
    return 'hh'

g1=gevent.spawn(eat,'我是1')# 创建一个协程的对象,第一个参数为函数名,[关键字参数/位置参数]
g2=gevent.spawn(play,'我是2')
g1.join()#等待g1结束,上面只是创建协程对象,这个join才是去执行
g2.join()#但是在执行上面的g1.join()之后,g2.join()也会自动执行的
print(g2.value)
#gevent.joinall([g1,g2]) #相当于两个join的作用
print('主程序结束')
'''
结果
我是1 eat 1*
我是2 play 1#
我是2 play 2##
我是1 eat 2**
hh
主程序结束
'''
# gevent.sleep(2)模拟的是gevent可以识别的io阻塞,
# time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
# 要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

# 注意的是,开启多个协程的话,如果所有的执行的代码都不存在I/O操作的话,协程没有存在的意义,基本是串行执行的
#
gevent的使用

1.4 gevent的应用

from gevent import monkey;monkey.patch_all()
import gevent
import requests
import time

def get_page(url):
    print('GET: %s' %url)
    response=requests.get(url)
    if response.status_code == 200:
        print('%d bytes received from %s' %(len(response.text),url))


start_time=time.time()
gevent.joinall([
    gevent.spawn(get_page,'https://www.python.org/'),
    gevent.spawn(get_page,'https://www.yahoo.com/'),
    gevent.spawn(get_page,'https://github.com/'),
])
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
gevent在爬虫中的应用
原文地址:https://www.cnblogs.com/vivi0403/p/10066999.html