20.selector/线程回顾
IO多路服用实现机制
队列(queue)
join和task_done方法
先进后出
生产者消费者模型
回顾
线程与进程
GIL锁:
Tread
如何创建
jion方法和setDaemon方法
同步锁
递归锁和死锁
selectors 模块
import selectors
# 基于select模块实现的IO多路复用,建议大家使用
import socket
sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(5)
sock.setblocking(False)
sel = selectors.DefaultSelector()
# 根据具体平台选择最佳IO多路机制,比如在linux上会优先选择epoll
# print(sel)
def read(conn, mask):
try:
data = conn.recv(1024)
print(data.decode('utf-8'))
conn.send(data.upper())
except Exception:
sel.unregister(conn)
def accept(sock, mask):
conn, addr = sock.accept()
print(conn)
sel.register(conn, selectors.EVENT_READ, read)
# 注册,传入socket对象,第三个参数为运行的函数
sel.register(sock, selectors.EVENT_READ, accept)
# 将sock和accept绑定到sel当中去
while 1:
# 监听
print('waiting data….')
event = sel.select() # 获取活动对象[(key,mask)]
for key, mask in event:
# print(key.data) # accept 函数
# print(key.fileobj) # 活动的soc对象
func = key.data
obj = key.fileobj
func(obj, mask)
IO多路服用实现机制
windows下 select
linux 下除了select之外还有poll 和epoll
select缺点
- 每次调用select都需要把所有的文件描述符(fd)拷贝到内核空间,导致效率下降
- 遍历所有的fd,是否有数据访问
- 最大连接数是有阈值的
poll:
最大连接数,没有限制
epoll:通过三个函数来实现
第一个函数:创建epoll句柄:将所有的fd拷贝到内核空间,但是只需要拷贝一次;
第二个函数:回调函数:某一个事件(函数/动作)结束的时候,会自动调用的函数。为所有的fd绑定一个回调函数,一旦有数据访问,触发该回调函数,回调函数将fd放到链表中;
队列(queue)
队列在多线程编程,多个线程安全交换数据的情况下尤其有用。
import queue
q = queue.Queue() #可以传入参数最大传入值
q.put(111)
q.put('hello')
q.put('super')
q.put('test',False) #如果取值只有三个,第四个数据进入的时候也会默认blocking,这时候添加False就会直接报错,而不是阻塞在这里
ret1 = q.get()
ret2 = q.get()
ret3 = q.get()
ret4 = q.get(True) #取值取完之后仍然想要取值,这时候会默认blocking(阻塞),在get中传入True后就会不阻塞,直接返回错误值empty
优点:线程安全
join和task_done方法
q = queue.Queue(5)
q.put(111)
q.put(333)
a = q.get()
b = q.get()
print(a,b)
q.task_done()
q.task_done()
#有几个操作就要有几个task_done,虽然这里只有一个print,但是task_done和print这种操作无关,只和队列相关的操作相关!
q.join()
print('over')
先进后出
queue.LifoQueue() # lifo last in first out
q.put(11)
q.put(22)
q.put(333)
print(q.get())
## 优先级
q = queue.PriortyQueue()
q.put([1,'hello1'])
q.put([2,'hello2'])
q.put([3,'hello3'])
q.put([4,'hello5'])
q.put([5,'hello4'])
while not q.empty:
q.get()
生产者消费者模型
回顾
线程与进程
为什么需要: 实现并发(切换----由操作系统实现)
进程:是最小的资源管理单位(可以理解为一个存放线程的容器,一个py程序里,最少有一个线程,就是主线程)
线程:是最小的执行单位
并发:同一时间只有一个线程在执行,但是分布式执行线程,可以让某个线程执行一段时间后切换给另外一个线程执行,交替进行。但是由于需要保存各个线程的执行状态,会有消耗
并行:多个任务同一时刻都在执行,调用了多核cpu
串行:同一时间只有一个线程在执行,执行完才进行下一个线程
多进程和多线程都可以执行多个任务,线程是最小的执行单位,进程是最小的资源单位,线程跑在进程中,一个进程中一定有一个线程。
线程的特点是线程之间可以共享内存和变量,资源消耗少(在Unix环境中,多进程和多线程资源调度小号差距不明显,Unix调度较快),缺点是线程之间的同步和枷锁比较麻烦。
GIL锁:
cpython的解释器因为存在GIL,导致:同一时刻,同一进程,只能有一个线程被执行
Tread
如何创建
jion方法和setDaemon方法
daemon:程序直到不存在非守护线程时退出
是不是可以理解为,当程序只剩下主线程和守护线程的时候就退出
同步锁
由于多线程处理公共数据
存在一个全局变量,所有的线程都可以调用操作这个变量。由于GIL锁,同一时间同一进程只有一个线程在执行。通过sleep模拟一个IO操作,由于线程1在执行IO操作,这时候切换到线程2,线程1的状态被保存。此时由于线程1在执行操作前出现IO操作,并没有来得及对数据进行操作,所以线程2获得的数据仍然是原来的全局变量。