Python实战之协程(greenlet模块,gevent模块,socket+ gevent实现高并发处理)

协程

协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。(cpu不知道,是用户自己控制的)

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈(线程的上下文切换是保存在CPU,协程就不是)。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置(yield生产消费就是协程)。

 

协程的好处

无需线程上下文切换的开销

无需原子操作锁定及同步的开销

  "原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。

方便切换控制流,简化编程模型

高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

扫描二维码关注公众号,回复: 4788766 查看本文章

无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

 

linux第三方库安装greenlet  gevent

pip3 install gevent

pip3 install greenlet 

greenlet(已经封装好的协程的模块(手动切换协程)

gevent(已经封装好的协程的模块(自动切换协程))

 

试验greenlet

__author__ = "Burgess Zheng"

from greenlet import greenlet#greenlet 已经封装好的协程
def test1():
    print(12)# 1.首先打印显示:12
    gr2.switch()#切换(启动)gr2该协程
    print(34)#3.打印显示:34
    gr2.switch()#切换(启动)gr2该协程
def test2():
    print(56)#2.打印显示:56
    gr1.switch()#切换(启动)gr1该协程
    print(78)#4.打印显示:78

gr1 = greenlet(test1) #实例化一个协程
gr2 = greenlet(test2) #实例化一个携程
gr1.switch()#切换(启动)gr1该协程 (现在开始执行test1协程)

执行结果:

 

试验Gevent

__author__ = "Burgess Zheng"

import gevent

def foo():
    print('Running in foo')     # 1
    gevent.sleep(2)
    print('Explicit context switch to foo again')#6
def bar():
    print('Explicit精确的 context内容 to bar')  #2
    gevent.sleep(1)
    print('Implicit context switch back to bar')#5
def func3():
    print("running func3 ")   #3
    gevent.sleep(0)
    print("running func3  again ")#4


gevent.joinall([         #一次性生成多个协程
    gevent.spawn(foo), #实例化生成一个自动协程
    gevent.spawn(bar),  #实例化生成一个自动协程
    gevent.spawn(func3), #实例化生成一个自动协程
])

执行结果:

试验协程并发爬网页 和 列表for调用同步串行爬网页的区别和效率

 

__author__ = "Burgess Zheng"

from urllib import request#简单爬虫
import gevent,time
from gevent import monkey#因为gevent不知道urllib内部是否在进行IO操作,
# 所以需要我们需要让gevent知道urllib在进程内部操作,就需要引入monkey
monkey.patch_all() #把当前程序的所有的io操作给我单独的做上标记

def f(url):
    print('GET: %s' % url)
    resp = request.urlopen(url)#获取该网址页面的内容
    data = resp.read()
    #f = open("url.html","wb")#创建文件url.html
    #f.write(data)#获得网址内容写入文件
    #f.close()#完成以后关闭
    print('%d bytes received from %s.' % (len(data), url))

#使用列表循环操作爬虫(同步串行)
urls = ['https://www.python.org/',
        'https://www.yahoo.com/',
        'https://github.com/' ]
time_start = time.time()#开始执行时间
for url in urls:
    f(url)
print("同步cost",time.time() - time_start)#当前时间减去开始执行时间=获得执行时间


#使用协程爬虫(效果是异步并行)
async_time_start = time.time()#异步开始执行时间
gevent.joinall([
    gevent.spawn(f, 'https://www.python.org/'),
    gevent.spawn(f, 'https://www.yahoo.com/'),
    gevent.spawn(f, 'https://github.com/'),
])
print("异步cost",time.time() - async_time_start)
#当前时间减去异步开始执行时间=获得执行时间

执行结果:

 

socket协程高并发处理试验(比Python自带的socketserver(线程)还NB)

socketserver

__author__ = "Burgess Zheng"

import sys
import socket
import time
import gevent

from gevent import socket, monkey

monkey.patch_all()


def server(port):
    s = socket.socket()#启动socket
    s.bind(('0.0.0.0', port))#绑定端口
    s.listen(500)#允许500个并发
    while True:
        cli, addr = s.accept()#接收到数据以后会返回2个值:链接标记位和对方的地址
        gevent.spawn(handle_request, cli)#启动线程handle_request 链接标记位作为实参

#这样每进来一个用户访问就启动一个协程,就代表可以处理高并发

def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)#接收数据
            print("recv:", data)#打印接收的数据
            conn.send(data)#返回数据
            if not data:#如果数据是空
                conn.shutdown(socket.SHUT_WR)#关闭客户端 ,用break也可以

    except Exception as  ex:#抓异常
        print(ex)#打印异常
    finally:#无论是否异常继续执行下面的
        conn.close()#关闭连接


if __name__ == '__main__':
    server(8001)

socketclient

__author__ = "Burgess Zheng"
import socket

HOST = 'localhost'  # The remote host
PORT = 8001  # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg = bytes(input(">>:"), encoding="utf8")
    s.sendall(msg)
    data = s.recv(1024)

    #
print('Received', repr(data))#repr格式输出 也可以直接data ,没鸟用
s.close()

执行结果:

 

 

猜你喜欢

转载自blog.csdn.net/Burgess_zheng/article/details/85842766
今日推荐