回顾
1.GIL全局解释器锁
1.本质上是一把互斥锁.
2.Cpython才有的
3.在同一个进程下开启多个线程,让并发变成串行,保证线程安全.
2.何时使用多线程或多进程:
- IO密集型:
使用多线程
- 计算密集型:
使用多进程
3.死锁现象
A,B两把锁,相互调用对方,导致出现死锁现象.
4.递归锁:
解决死锁问题.
普通锁:
只能被一个线程去引用.
递归锁:
可以被多个线程去引用.
当递归锁的引用计数为0才会释放,让其他任务去抢.
5.信号量
互斥锁:
一次只能被一个线程去使用.
信号量(本质上是一把锁):
一次可以被多个线程去使用.
6.线程队列:
- FIFO: 先进先出
- LIFO: 后进先出
- 优先级队列: 根据数字的大小进行判断优先级, 数字越小优先级越高.
7.TCP服务端实现并发
- 服务端:
- 让连接客户端由串行变成并发
Event事件
Event事件的作用:
- 用来控制线程的执行.
- 由一些线程去控制另一些线程.
from threading import Event from threading import Thread import time # 调用Event类实例化一个对象 e = Event() # 若该方法出现在任务中,则为False,阻塞 # e.wait() # False # 若该方法出现在任务中,则将其他线程的Flase改为True,进入就绪态与运行态 # e.set() # True def light(): print('红灯亮...') time.sleep(5) # 应该开始发送信号,告诉其他线程准备执行 e.set() # 将car中的False ---> True print('绿灯亮...') def car(name): print('正在等红灯....') # 让所有汽车任务进入阻塞态 e.wait() # False print(f'{name}正在加速漂移....') # 让一个light线程任务 控制多个car线程任务 t = Thread(target=light) t.start() for line in range(10): t = Thread(target=car, args=(f'童子军jason{line}号', )) t.start()
线程池与进程池
1)什么是进程池与线程池?
进程池与线程池是用来控制当前程序允许创建(进程/线程)的数量.
2)进程池与线程池的作用:
保证在硬件允许的范围内创建 (进程/线程) 的数量.
3)如何使用:
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import time # ProcessPoolExecutor(5) # 5代表只能开启5个进程 # ProcessPoolExecutor() # 默认以CPU的个数限制进程数 pool = ThreadPoolExecutor(5) # 5代表只能开启5个线程 -5 +1 -1 +1 -1 # ThreadPoolExecutor() # 默认以CPU个数 * 5 限制线程数 # t = Tread() # 异步提交 # t.start(0) # pool.submit('传函数地址') # 异步提交任务 # def task(): # print('线程任务开始了...') # time.sleep(1) # print('线程任务结束了...') # # # for line in range(5): # pool.submit(task) # 异步提交任务 # pool.submit('传函数地址').add_done_callback('回调函数地址') def task(res): # res == 1 print('线程任务开始了...') time.sleep(1) print('线程任务结束了...') return 123 # 回调函数 def call_back(res): print(type(res)) # 注意: 赋值操作不要与接收的res同名 res2 = res.result() print(res2) for line in range(5): pool.submit(task, 1).add_done_callback(call_back) # 会让所有线程池的任务结束后,才往下执行代码 # pool.shutdown() print('hello')
协程
进程: 资源单位
- 线程: 执行单位
- 协程: 在单线程下实现并发注意: 协程不是操作系统资源,他是程序起的名字,为让单线程能实现并发.
协程的目的:
- 操作系统:
多道技术, 切换 + 保存状态
1) 遇到IO
2) CPU执行时间过长协程:
通过手动模拟操作系统 "多道技术",实现 切换 + 保存状态
1)手动实现 遇到IO切换, 欺骗操作系统误以为没有IO操作.
- 单线程 遇到IO, 切换 + 保存状态 - 单线程 计算密集型, 来回切换 + 保存状态是,反而效率更低
优点:
在IO密集型的情况下, 会提高效率.缺点:
若在计算密集型的情况下, 来回切换, 反而效率更低.
- 如何实现协程: 切换 + 保存状态
- yield: 保存状态
- 并发: 切换
# # # 串行执行 # # import time # # # # # # def func1(): # # for i in range(10000000): # # i+1 # # # # # # def func2(): # # for i in range(10000000): # # i+1 # # # # # # start = time.time() # # func1() # # func2() # # stop = time.time() # # print(stop - start) # 0.8930509090423584 # 验证计算密集型的情况下效率更低: # 1.4250171184539795 # 基于yield并发执行 # import time # # # def func1(): # while True: # 10000000+1 # yield # # # def func2(): # # g生成器对象 # g = func1() # for i in range(10000000): # time.sleep(100) # 模拟IO,yield并不会捕捉到并自动切换 # i+1 # next(g) # # # start = time.time() # func2() # stop = time.time() # print(stop-start) '''gevent: 是一个第三方模块,可以帮你监听IO操作, 并切换. - 使用gevent目的: 为了实现单线程下,实现遇到IO, 保存状态 + 切换 pip3 install gevent ''' from gevent import monkey monkey.patch_all() # 可以监听该程序下所有的IO操作 import time from gevent import spawn, joinall # 用于做切换 + 保存状态 def func1(): print('1') # IO操作 time.sleep(1) def func2(): print('2') time.sleep(3) def func3(): print('3') time.sleep(5) start_time = time.time() s1 = spawn(func1) s2 = spawn(func2) s3 = spawn(func3) # s2.join() # 发送信号,相当于等待自己 (在单线程的情况下) # s1.join() # s3.join() # 必须传序列类型 joinall([s1, s2, s3]) end_time = time.time() print(end_time - start_time)