协程

进程:启动多个进程,进程之间是由操作系统负责调用
线程:启动多个线程,真正被CPU执行的最小单位。其他语言里面可以多个线程使用多个CPU,但是python不行。
开启一个线程,创建一个线程和关闭一个线程都需要浪费时间。
协程:
本质上是一个线程;其能够在多个任务之间切换来节省一些IO时间。协程中任务之间的切换也小号时间,但是开销要远远小于进程和线程之间切换的时间浪费

什么是在多个程序之间进行切换?

def consumer():
    while 1:#这是一个生成器
        x=yield
        print('处理了:',x)

def producer():
    c=consumer()
    next(c)
    for i in range(20):
        print('生产了数据:',i)
        c.send(i)
producer()
#利用prodcer函数最终控制了consumer函数,这就是两个程序之间来回的切换

上面中是利用yield实现了切换,但是实际上在协程中是利用greenlet来实现的,具体如下

from greenlet import greenlet

def eat():
    print('eating start')#这是第二步
    g2.switch()#第三步
    print('eating end')#第六步
    g2.switch()#第七步


def play():
    print('play start')#第四步
    g1.switch()#第五步
    print('play end')#第八步


g1=greenlet(eat)
g2=greenlet(play)
g1.switch()#这里切换到g1,这是第一步

注意协程的运作机理:当计算机在执行IO操作的时候,此时计算机的处理器暂时处于停顿状态,那么这对于计算机而言是资源浪费。利用协程能够将需要CPU处理的程序切换到这段空闲时间内,进而最大利用CPU 的执行效率,缩短程序执行的时间。

注意进程到线程再到协程,就是程序一步步的不断压缩CPU的暂停时间,只不过在进程和线程中是由操作系统来完成调度,而在协程中是由程序来完成而已。在协程中切换是由geenlet模块完成,最后使用的时候是使用Gevent模块,但是已经将greenlet模块封装在其中了。

一个4核CPU:

最多可以启动5个进程,

20个线程

每个线程最多可以开启500个协程

这样计算下来就是一个四核CPU最多可以开启5万个协程

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

import gevent
import time

def eat():
    print('eating start')
    time.sleep(1)
    print('eating end')
    g2.switch()

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

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

结果依然是

eating start
eating end
play start
play end

说明gvent基本上忽略了time.sleep()

import gevent
import time

def eat():
    print('eating start')
    gevent.sleep(1)
    print('eating end')
    g2.switch()

def play():
    print('play start')
    gevent.sleep(1)
    print('play end')

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

结果依然是

eating start

play start
eating end
play end

说明gevent.sleep起了作用

from gevent import monkey;monkey.patch_all()#将执行过程中的阻塞都打成一个包
import gevent
import time
import threading

def eat():
    print(threading.current_thread().getName())#输出当前线程的名字,结果是DummyThread-1,而之前线程的名字直接就是Thread-1,Dummy由虚假假冒的意思,所以表示这并不是一个真的线程
    print('eating start')
    time.sleep(1)
    print('eating end')
    g2.switch()

def play():
    print(threading.current_thread().getName())  # 输出当前线程的名字
    print('play start')
    time.sleep(1)
    print('play end')

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

上例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()放到文件的开头

同步和异步

from gevent import monkey;monkey.patch_all()#将执行过程中的阻塞都打成一个包
import gevent
import time

def task():
    time.sleep(1)
    print('123')

def sync():#同步
    for i in range(5):
        task()

def asyncs():#异步
    g_list=[]
    for i in range(20):
        g=gevent.spawn(task)
        g_list.append(g)
    gevent.joinall(g_list)#等价于for g in g_list:g.join()

sync()
asyncs()

注意:只有当程序中有IO操作的时候使用协程才有价值,否则程序之间的切换也会造成效率的降低。所以一般在网络编程中使用协程比较多

爬虫:

import requests
def get_url(url):
    res=requests.get(url)
    content=res.content.decode('utf-8')
    return len(content)

res1=get_url('https://www.baidu.com/')
print(res1)

在这个过程中,首先进程需要访问自己电脑的操作系统,一次IO操作

自己的操作系统分配一个端口通过网络协议访问百度,一次IO操作

百度服务器的操作系统访问到具体的内容,一次IO操作。

返回同样需要这三次IO操作,所以在整个过程中需要6次IO操作,那么就可以使用协程来提高效率

 

from gevent import monkey;monkey.patch_all()
import gevent
import requests
def get_url(url):
    res=requests.get(url)
    content=res.content.decode('utf-8')
    return len(content)

g1=gevent.spawn(get_url,'http://www.baidu.com/')
g2=gevent.spawn(get_url,'http://www.sohu.com/')
g3=gevent.spawn(get_url,'http://www.sina.com/')
g4=gevent.spawn(get_url,'http://www.taobao.com/')
g5=gevent.spawn(get_url,'http://www.ifeng.com/')

gevent.joinall([g1,g2,g3,g4,g5])
print(g1.value)
print(g2.value)
print(g3.value)
print(g4.value)
print(g5.value)

通信

sever

from gevent import monkey;monkey.patch_all()
import gevent
import socket
def func():
    '''注意下面的这些代码就相当于具体干活的人'''
    conn.send(b'hello')
    ret = conn.recv(1024).decode('utf-8')
    print(ret)
    conn.close()

sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while 1:
    conn, addr = sk.accept()#这就相当于一个接线员,他只负责接线,把任务派发出去,那么下面func(),执行func函数就代表将接到的活派发出去,被执行了
    g1=gevent.spawn(func)#,那么这里就可以使用协程

sk.close()

clent

import socket
sk=socket.socket()
sk.connect(('127.0.0.1',8080))
print(sk.recv(1024).decode('utf-8'))
msg=input('>>>').encode('utf-8')
sk.send(msg)
sk.close()

 

posted @ 2019-04-17 23:38  舒畅123  阅读(174)  评论(0编辑  收藏  举报