队列
import queue q = queue.Queue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) """ 1 2 3 """
栈
t = queue.LifoQueue(4) t.put(1) t.put(2) t.put(3) t.put(4) print(t.get()) print(t.get()) print(t.get()) print(t.get()) """ 4 3 2 1 """
优先级队列
p = queue.PriorityQueue(4) p.put((5,"oifjanior")) p.put((-2,"hshts")) p.put((0,"shtr")) p.put((3,"hstrhs")) print(p.get()) print(p.get()) print(p.get()) print(p.get()) """ (-2,"hshts") (0,"shtr") (3,"hstrhs") (5,"oifjanior") """
事件event
如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作
版本一
from threading import current_thread, Thread import time flag = False def check(): print(f"{current_thread().name}监测服务器是否开启") time.sleep(3) global flag flag = True print("服务器已经开启") def connect(): while 1: print(f"{current_thread().name}等待连接...") time.sleep(0.5) if flag: print(f"{current_thread().name}连接成功...") break t1 = Thread(target=check) t2 = Thread(target=connect) t1.start() t2.start() """ Thread-1监测服务器是否开启 Thread-2等待连接... Thread-2等待连接... Thread-2等待连接... Thread-2等待连接... Thread-2等待连接... Thread-2等待连接... 服务器已经开启 Thread-2连接成功... """
版本二
from threading import Thread, current_thread, Event import time event = Event() def check(): print(f"{current_thread().name} 监测服务器是否开启...") time.sleep(3) print(event.is_set()) # 判断事件是否开启 event.set() print(event.is_set()) print("服务器已经开启...") def connect(): print(f"{current_thread().name} 等待连接...") event.wait() # 阻塞, 直到 event.set() 方法之后 # event.wait(1) 只阻塞1秒, 1秒之后如果还没有进行set 直接进行下一步操作 print(f"{current_thread().name}连接成功...") t1 = Thread(target=check) t2 = Thread(target=connect) t1.start() t2.start()
开启两个线程, 一个线程运行到中间的某个阶段, 触发另一个线程执行. 两个线程增加了耦合性
练习
一个线程监测服务器是否开始, 另一个线程判断如果开始了, 则显示连接成功, 此线程只尝试连接3次, 1s一次, 如果超过3次, 还没有连接成功, 则显示连接失败.
from threading import current_thread,Thread,Event import time event = Event() def check(): print(f"{current_thread().name}监测服务器是否开启...") time.sleep(4) event.set() print("服务器已开启") def connect(): count = 1 while not event.is_set(): if count == 4: print("连接次数过多, 已断开") break event.wait(1) print(f"{current_thread().name}尝试连接{count}次") count += 1 else: print(f"{current_thread().name}连接成功..") t1 = Thread(target=check) t2 = Thread(target=connect) t1.start() t2.start()
协程
一个线程并发的处理任务
串行:
一个线程执行一个任务, 执行完毕之后, 执行下一个任务.
并行:
多个CPU执行多个任务, 4个CPU执行4个任务
并发:
一个CPU执行多个任务, 看起来像是同时运行
并发的本质:
切换 + 保持状态
多线程并发图
3个线程处理10个任务, 如果线程1处理的这个任务, 遇到阻塞, CPU被操作系统切换到另一个线程
单线程并发图
一个线程处理三个任务
并发方式:
单个CPU, 并发执行10个任务
1.开启多进程并发执行, 操作系统切换 + 保持状态
2.开启多线程并发执行, 操作系统切换 + 保持状态
3.开启协程并发的执行, 自己的程序把控着CPU, 在3个任务之间来回切换 + 保持状态
详解第三种方式
协程切换速度非常快, 蒙蔽操作系统的眼睛, 让操作系统认为CPU一直在运行这一个线程(协程)
最好的方式
协程, 因为开销小, 运行速度快, 协程会长期霸占CPU只执行我程序里面的所有任务
利用协程处理IO密集型效率高, 计算密集型, 还是使用串行
协程
单个线程并发的处理多个任务, 程序控制协程的切换 + 保持状态
切换
def func1(): print("in func1") def func2(): print("in func2") func1() print("end") func2()
切换 + 保留状态
def gen(): while 1: yield 1 def func(): obj = gen() for i in range(10): next(obj) func()
协程
import gevent from gevent import monkey monkey.patch_all() def eat(name): print(f"{name} eat 1") gevent.sleep(2) print(f"{name} eat 2") def play(name): print(f"{name} play 1") gevent.sleep(1) print(f"{name} play 2") g1 = gevent.spawn(eat, "egon") g2 = gevent.spawn(play, "egon") gevent.joinall([g1,g2])
协程的特点
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁
3.用户程序里自己保存多个控制流的上下文栈(保持状态)
4.附加: 一个协程遇到IO操作自动切换到其他协程
工作中
一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块