python之路 -- 协程

  单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。

1.协程

--本质上是一个线程

--能够在多个任务之间切换来节省一些IO时间

--协程中任务之间的切换也消耗时间,但是开销要远远小于进程线程之间的切换

2.用yield实现任务的切换

--yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级

--send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换

import time
def consumer():
    while True:
        x = yield
        time.sleep(1)
        print('处理了数据 :',x)

def producer():
    c = consumer()
    next(c)
    for i in range(10):
        time.sleep(1)
        print('生产了数据 :',i)
        c.send(i)

producer()

3.greenlet模块实现协程

from greenlet import greenlet
def eat():
    print('eating start')
    g2.switch()           
    # 转换到函数play并保存执行进度,当再转换到此函数时,接着此处执行
    print('eating end')
    g2.switch()

def play():
    print('playing start')
    g1.switch()
    print('playing end')

g1 = greenlet(eat)    
# 将eat函数交给greenlet执行,赋给g1,当调用g1.switch()时就去执行eat函数
g2 = greenlet(play)
g1.switch()   # 执行函数eat

"""执行结果为:
eating start
playing start
eating end
playing end
"""

4. gevent模块实现协程

from gevent import monkey;monkey.patch_all()
# 这句代码的作用就是让协程的这个模块认识其他模块中的IO操作,然后再执行过程中遇到IO操作进行跳转。必须在第一句
import gevent
import time
def eat():
    print('eating start')
    time.sleep(1)
    print('eating end')

def play():
    print("playing start")
    time.sleep(1)
    print('playing end')

g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()

"""执行结果为:
eating start
playing start
eating end
playing end
"""

# 进程和线程的任务切换由操作系统完成

# 协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的IO操作的时候,程序才会进行任务切换,实现并发的效果

 同步 和 异步
from gevent import monkey
monkey.patch_all()
import time
import gevent

def task(n):
    time.sleep(0.5)
    print(n)

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

def async():
    g_lst = []
    for i in range(10):
        g = gevent.spawn(task,i)
        g_lst.append(g)
    gevent.joinall(g_lst)  # for g in g_lst:g.join()

sync()  # 同步
async() # 异步

协程 : 能够在一个线程中实现并发效果的概念

   能够规避一些任务中的IO操作

   在任务的执行过程中,检测到IO就切换到其他任务

   协程 在一个线程上 提高CPU 的利用率

   协程相比于多线程的优势 切换的效率更快

# 爬虫的例子
# 请求过程中的IO等待
from gevent import monkey;monkey.patch_all()
import gevent
from urllib.request import urlopen    # 内置的模块
def get_url(url):
    response = urlopen(url)
    content = response.read().decode('utf-8')
    return len(content)

g1 = gevent.spawn(get_url,'http://www.baidu.com')
g2 = gevent.spawn(get_url,'http://www.sogou.com')
g3 = gevent.spawn(get_url,'http://www.taobao.com')
g4 = gevent.spawn(get_url,'http://www.hao123.com')
g5 = gevent.spawn(get_url,'http://www.cnblogs.com')
gevent.joinall([g1,g2,g3,g4,g5])
print(g1.value)
print(g2.value)
print(g3.value)
print(g4.value)
print(g5.value)

# 几乎同时得到输出结果
爬虫的例子

5.协程实现socketserver

  server端

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

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

def talk(conn):
    conn.send(b'hello')
    print(conn.recv(1024).decode('utf-8'))
    conn.close()

while True:
    conn,addr = sk.accept()
    gevent.spawn(talk,conn)

sk.close()
 
View Code

  client端

import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))

msg = sk.recv(1024).decode('utf-8')
print(msg)
info = input('>>>').encode('utf-8')
sk.send(info)
sk.close()
View Code

 

posted @ 2018-08-13 10:23  Aberwang  阅读(104)  评论(0)    收藏  举报