第四十一天:协程操作

1.无论是进程还是线程都是由操作系统的时间片时间来进行操控,无法进行人为的控制,并且进行的都是并发程序。从微观上看还是同一时间执行一个程序。

2.进程是资源分配的最小单位,线程是cpu调度的最小单位。在开启线程的过程中,创建一个线程需要创建一个寄存器和堆栈,这些都是要花费时间的。

3。协程操作:为了实现并发操作:主要是为了多个任务之间进行切换。

4.以前学的生产者消费者模型里就含有这种模式的模型:

def consumer():
    while True:
        x=yield
        print('处理了数据',x)
def production():
    c=consumer()
    next(c)
    for i in range(10):
        print('生产了数',i)
        c.send(i)
production()
View Code

5.装greenlet模块:打开cmd然后输入C:——enter——输入pip install greenlet 就可以下载了

6.安装gevent:步骤和上面一样就是输入 pip install  gevent

7.协程的第一个列子:

from greenlet import greenlet
import time
def eat():
    print('eat start')
    time.sleep(1)
    print('eat end')
def play():
    print('play start')
    time.sleep(1)
    print('play end')
g1=greenlet(eat)#只是进行实例化,并没有执行该函数
g2=greenlet(play)
View Code

 

 从结果可以看出没有执行任何指令:

8.使用switch才能执行函数里的指令(此语句代表的意思是切换)

from greenlet import greenlet
import time
def eat():
    print('eat start')
    time.sleep(1)
    g2.switch()#记住执行的位置,并切换到play去执行
    print('eat end')
def play():
    print('play start')
    time.sleep(1)
    g1.switch()#记住执行的位置,并切换到play去执行
    print('play end')
g1=greenlet(eat)#只是进行实例化,并没有执行该函数
g2=greenlet(play)
g1.switch()#切换到eat函数去执行
结果为
eat start
play start
eat end
View Code

从结果可以看出切换到eat执行完成之后并由切换到play执行最后的语句,使用的解决方法是:

9.计算时间执行和协程切换执行花费的时间:

  顺序执行的时间:

import time
from greenlet import greenlet
def func():
    for i in range(10000000):
        i*i
def func1():
    for i in range(10000000):
        i+i
start=time.time()
func()
func1()
t1=time.time()-start
print(t1)
结果为
1.0864977836608887
View Code

  使用协程操作的时间

import time
from greenlet import greenlet
def func():
    for i in range(10000000):
        i*i
        g2.switch()
def func1():
    for i in range(10000000):
        i+i
        g1.switch()
start=time.time()
g1=greenlet(func)
g2=greenlet(func1)
g1.switch()
g2.switch()
t1=time.time()-start
print(t1)
结果为
5.035648822784424
View Code

从两个结果可以看出每次协程之间的转换是要花费时间的。

10.协程主要用于高IO操作中,当程序在执行过程中如果遇到IO操作,就会进入堵塞状态,程序无法进行下面的流程,如果把这个时间空出来供另一个协程使用,这样就提高了资源的利用率

11.一般来说进程一般为cpu个数+1 线程为cpu个数的5倍,协程一般最多开500个

12.虽然协程在切换期间会浪费时间,但是浪费的时间远远小于堵塞的时间。

13.协程的第个操作指令:gevent

g1=gevent.spawn(func,参数)创建一个协程对象g1,spawn括号内第一个参数是函数名
后面的可以是位置参数也可以是关键字参数。
g1.join()  #等待g1的结束
g2.join()
或者使用gevent.joinall([g1,g2])
g1.value 拿到返回值
View Code

14.可以使用from gevent import monkey;monkey.patch_all(),把所有可以发生堵塞的模块进行打包,并且此模块一定要放到所有导入模块的最前面

from gevent import monkey;monkey.patch_all()
import gevent
import time
def eat():
    print('eat start')
    time.sleep(1)
    print('eat end')
def play():
    print('play start')
    time.sleep(1)
    print('play end')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
g1.join()
g2.join()
结果为
eat start
play start
eat end
play end
View Code

15。协程的执行是否依靠join操作

from gevent import monkey;monkey.patch_all()
import gevent
import time
def eat():
    print('eat start')
    time.sleep(1)
    print('eat end')
def play():
    print('play start')
    time.sleep(1)
    print('play end')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
g1.join()
View Code

从结果我们可以看出只有启动一个协程就可以执行所有实例化的协程

16.协程里面的任务切换是通过greenlet中的switch进行的。

17进程和线程里的切换操作都是由操作系统控制完成的,而协程里的切换操作是由程序操作的(代码)。

18只有遇到高要求的IO操作时,我们才使用协程操作,实行并发的效果。

19.查看协程号的程序:

from gevent import monkey;monkey.patch_all()
import gevent
import time
import threading
def eat():
    print('eat start')
    print(threading.current_thread(),threading.get_ident)
    time.sleep(1)
    print('eat end')
def play():
    print('play start')
    print(threading.current_thread(),threading.get_ident)
    time.sleep(1)
    print('play end')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
g1.join()
结果为
eat start
<_DummyThread(DummyThread-1, started daemon 1840173395744)> <function get_ident at 0x000001AC72E580D0>
play start
<_DummyThread(DummyThread-2, started daemon 1840177303776)> <function get_ident at 0x000001AC72E580D0>
eat end
play end
View Code

从结果来看协程是假的线程,但是两个协程都来自同一个协程。

19.同步和异步的例子(就是使用协程和不使用协程):

from gevent import monkey;monkey.patch_all()
import gevent
import time
import threading
def task():
    time.sleep(1)
    print(1234)
def same():
    for i in range(10):
        task()
def different():
    g_list=[]
    for i in range(10):
        g1=gevent.spawn(task)
        g_list.append(g1)
    gevent.joinall(g_list)
same()
different()
View Code

从结果来看,同步是一个一个执行,异步是一次性输出所有接过执行原理是,当遇到sleep时,程序进入堵塞,协程这个时候就会切换到下面一次程序进行,然后继续堵塞,继续切换,等到哪个程序执行完成后再进行切换,切换过来以后,那写程序sleep时间都到了,所以可以实现并发的效果。

20.协程主要是能够在一个线程中实现并发效果。能够规避一些任务中的IO操作‘在任务执行的过程当中,检测到IO操作就可以切换到其他任务去执行。

21.协程的操作主要应用与爬虫和请求IO的等待。

22.爬虫的小例子:

from gevent import monkey;monkey.patch_all()
import gevent
import time
import requests
def acquire_url(url):
    ret=requests.get(url)
    ret=ret.content.decode('utf-8')
    return len(ret)
g1=gevent.spawn(acquire_url,'http://www.baidu.com')
g2=gevent.spawn(acquire_url,'http://www.sougou.com')
g3=gevent.spawn(acquire_url,'https://cn.bing.com/?mkt=zh-CN&mkt=zh-CN&mkt=zh-CN')
g4=gevent.spawn(acquire_url,'https://www.sohu.com/')
g5=gevent.spawn(acquire_url,'https://www.so.com/')
gevent.joinall([g1,g2,g3,g4,g5])
print(g1.value)
print(g2.value)
print(g3.value)
print(g4.value)
print(g5.value)
View Code

23使用协程来写socket的程序:

server端程序:

from gevent import monkey;monkey.patch_all()
import socket
import gevent
def connection(conn):
    conn.send('你好'.encode('utf-8'))
    msg=conn.recv(1024).decode('utf-8')
    print(msg)

sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while True:
    conn,addr=sk.accept()
    g1=gevent.spawn(connection,conn)
View Code

  client端程序:

import socket
sk=socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    msg=sk.recv(1024).decode('utf-8')
    print(msg)
    info=input('请输入信息').encode('utf-8')
    sk.send(info)
View Code

协程执行的过程就是把每一次获取到的结果放到一片空间中,等遇到堵塞了再去执行下一个协程。

 

posted @ 2020-03-20 15:27  chown  阅读(286)  评论(0编辑  收藏  举报