协程理解与运用

1. 协程概念

协程,又称微线程,纤程,英文名Coroutine。协程的作用,是在执行函数A时,可以随时中断,
去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调
用语句),这一整个过程看似像多线程,然而协程只有一个线程执行。

2. 协程优势

- 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,
没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
- 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共
享资源时也不需要加锁,因此执行效率高很多。


说明:协程可以处理 IO 密集型程序的效率, 但是处理 CPU 密集型不是它的长处,
如要充分发挥 CPU 利用率可以结合多进程 + 协程。

3.协程的实现

# 实现协程的第一种方法
import time
from collections import Iterable

def job():
    for i in range(10):
        print(i)
        yield 'result : %s'%(i)

# 函数里面包含yield关键字, 调用函数返回的是生成器对象;
# yield工作原理: 如果遇到yield就停止运行, 调用next方法, 从yield停止的地方继续运行;
j = job()
for i in j:
    print(i)

#  实现协程的第二种方法
"""
gevent是第三方库,通过greenlet实现协程,其基本思想:
    当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,
    等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常
    使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在
    运行,而不是等待IO。



Gevent使用说明
    monkey:             可以使一些阻塞的模块变得不阻塞,机制:遇到IO操作则自动切换,
                        手动切换可以用gevent.sleep()或者yield;
    gevent.sleep(0)     (将爬虫代码换成这个,效果一样可以达到切换上下文)
    gevent.spawn        启动协程,参数为函数名称,参数名称
    gevent.joinall      等待所有的协程执行结束;
    gevent.join()
"""
import threading
import gevent
from gevent import monkey

# 可以使一些阻塞的模块变得不阻塞,修改python内置的标准库;
monkey.patch_all()

def job(n):
    for i in range(n):
        print(gevent.getcurrent(),i,n)
        print('当前线程数:',threading.active_count())
        gevent.sleep(1)

def main():
    # 创建协程,分配任务
    g1=gevent.spawn(job,1)
    g2=gevent.spawn(job,2)
    g3=gevent.spawn(job,3)

    print("当前线程数:", threading.active_count())
    gevent.joinall([g1, g2, g3])
    print("任务执行结束.....")
if __name__ == '__main__':
    main()


#---->输出:
当前线程数: 1
 0 1
当前线程数: 1
 0 2
当前线程数: 1
 0 3
当前线程数: 1
 1 2
当前线程数: 1
 1 3
当前线程数: 1
 2 3
当前线程数: 1
任务执行结束.....

协程案例:爬取页面内容的大小

import threading
import gevent
from gevent import monkey
from urllib.request import urlopen
from urllib.error import HTTPError
import functools
import time

def timeit(f):
    @functools.wraps(f)
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=f(*args,**kwargs)
        end_time=time.time()
        print('%s函数运行的时间是%.2f' %(f.__name__,end_time-start_time))
        return res
    return wrapper

monkey.patch_all()

def get_page_length(url):
    try:
        urlobj=urlopen(url)
    except HTTPError as e:
        print('捕获失败!')
    else:
        length=len(urlobj.read())
        # print("%s长度为%d" %(url, length))

# 做实验, 访问本地url, 因为获取网络的url与网速有关(网速不稳定)
# 可以自己编写几个简单html页面
urls=['file:///home/kiosk/PycharmProjects/NO_1/index.html',
      'file:///home/kiosk/PycharmProjects/NO_1/login.html']*3000

# 贴吧网址
# urls=['http://tieba.baidu.com/f?fr=wwwt&kw=%E5%9B%9B%E5%85%AD%E7%BA%A7']

@timeit
def use_thread():
    threads=[]
    for url in urls:
        t=threading.Thread(target=get_page_length,args=(url,))
        threads.append(t)
        t.start()
    [thread.join() for thread in threads]
    print('线程执行结束.....')

@timeit
def use_gevent():
    gevents=[gevent.spawn(get_page_length,url) for url in urls]
    gevent.joinall(gevents)
    print('协程执行结束.....')

if __name__ == '__main__':
    use_thread()
    use_gevent()
#---->输出:
线程执行结束.....
use_thread函数运行的时间是1.53
协程执行结束.....
use_gevent函数运行的时间是1.16

你可能感兴趣的:(协程理解与运用)