一、eventlet是什么
eventlet - 具有WSGI支持的异步框架
eventlet是python库函数,是一个处理和网络相关的,另一个可以通过协程实现并发
可以实现’并发’(绿色线程),非阻塞
对Python库函数改写,支持协程
二、进程、线程、协程
进程和线程
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
进程是系统进行资源分配和调度的一个独立单位
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。
进程与线程的区别
- 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
- …
并发和并行
并发
在单线程中,由于只有一个线程A,因此只需从头运行到任务结束。
在多线程中,大部分操作系统的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行,直到所有线程的任务。
由于任务切换的速度特别快,所以从表面上的来看,好像是多个任务在同时运行。但是实际上,他们并没有在同时运行,只是把任务分成了多个小段,轮流的进行切换,这就是并发。
并行
假如有两个CPU,把两个任务同时分配给不同的CPU,这是他们在在同时地计算,运行任务,没有任务切换的过程,这叫并行。
例子
假设,需要挖三口井,挖一口井需要60分钟,
-
只有1个人,一个一个地挖,需要60+60+60=180分钟,这是单线程
-
只有1个人,第一个井挖1分钟后,马上换到第二个井挖1分钟,之后又马上换到第三个井挖1分钟,之后又回到第一个井。(忽略切换的时间)过程: 1->2->3->1->…。
如果把间隔的时间缩小到很短,从宏观上来看,就能看到3口井好像是在同时加工,实际上花费的时间依旧是180分钟。这个就是多线程 -
有3个人,3个人同时运作,处于一个并行工作的状态,只需60分钟就能完成。这就是并行
该例子中的1个人就是1个处理器(CPU),
当CPU个数>=任务数才是真正的并行。
而多线程则是一个并发的过程。
协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。从技术的角度来说,“协程就是你可以暂停执行的函数”。
线程是被分割的CPU资源
协程运行在单线程中,协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源
python 中可以使用greenlet
实现协程,这里只有一个主线程,有2个greenlet存在于主线程中。
from greenlet import greenlet
def T1():
print("T1.1")
gr2.switch() #切换到gr2 (即进入函数T2()中)
print("T1.2")
gr2.switch()
def T2():
print("T2.1")
gr1.switch()#切换到gr1 (即进入函数T1()中)
print("T2.2")
gr1 = greenlet(T1)
gr2 = greenlet(T2)
gr1.switch() #先执行gr1 ,即T1()
运行结果
T1.1
T2.1
T1.2
T2.2
- 程序入口使用2个greenlet()定义了2个协程,调用
gr1.switch()
进入T1()
函数中。 - 先执行了
print("T1.1")
,打印了T1.1
,之后执行gr2.switch()
,此时保存T1()
的运行状态,进入了T2()
函数,并打印T2.1
- …
- 一直执行以上过程,分别打印出
T1.2
和T2.2
可以看到,协程的工作流程和线程类似,都需要进行任务间的切换
不同之处是,
线程的任务调度是通过操作系统来执行的,此时需要消耗CPU的资源,由此容易产生资源浪费。
而协程(绿色线程),其任务调度由用户决定,且值同时存在于一个进程之中。
相比于多线程的任务调度,减少了线程切换的成本
协程的优点:
- 无须线程上下文切换的开销;
- 无须原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型;
- 高并发性、高扩展性、低成本;
协程的缺点:
- 无法利用多核资源:协程的本质就是单线程
由于协程资源消耗小,其在网络操作中使用非常广泛
三、eventlet概述
eventlet
是Python在 greenlet
(协程) 的基础之上,通过协程实现的并发操作库,同时可以处理网络相关操作
import eventlet
# from greenlet import greenlet
def handle(client):
print("有连接进入")
while True:
c = client.recv(1024)
if not c:
break
print(c)
def socket_test():
server = eventlet.listen(('0.0.0.0', 6000))
pool = eventlet.GreenPool(10000) # 设置可同时连接的客户端格式
while True:
new_sock, address = server.accept()
pool.spawn_n(handle, new_sock)
socket_test()
上述代码通过eventlet
,开启了一个socket服务端
new_sock, address = server.accept() # 接收客户端
pool.spawn_n(handle, new_sock) # 把客户端加入连接池中
这2行代码,在有客户端的加入时,就开启一个绿色线程,加入连接池中
对于一般的server(即不使用eventlet),若同时有多个客户端同时加入,server.accept()
会造成堵塞。一个一个地接收客户端。
而使用eventlet
创建的server时,创建的服务器套接字是经过绿化的,所以当多个连接到来时在server.accept()
这里不会阻塞,而是并行接收
client客户端
这里使用evenlet封装的socket,对原始的socket库进行了绿化,
同时需要使用eventlet.monkey_patch
对需要用到的系统的socket
,select
打补丁
eventlet.monkey_patch(socket=True, select=True)
eventlet.monkey_patch
可以打补丁的库,这里只使用socket
和select
import eventlet
from eventlet.green import socket
eventlet.monkey_patch(socket=True, select=True)
HOST = '127.0.0.1'
PORT = 6000
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpCliSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpCliSock.connect(ADDR)
tcpCliSock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpCliSock2.connect(ADDR)