实现并发的方式:
- 多线程
- 多进程
- 协程
要学习 锁 和 队列
1
线程对操作系统可见,但对用户不可见
线程是操作系统的最小执行单元,每个进程至少有一个线程(主线程)
当线程对象一旦被创建,用start()
方法开始,这会在独立的控制线程调用run()
方法。
继承Thread
- 继承Thread类
- 重写run方法:指定线程的工作任务,当任务完成后线程结束
- 调用start方法:改变线程的状态,使操作系统执行线程的任务
实例化Thread
- 实例化Thread,并传递把任务传递给target参数
- 调用start方法:改变线程的状态,使操作系统执行线程的任务
使用线程池
- 实例化ThreadPoolExecutor,把最大线程数作为参数传递
max_workers
- 提交任务:
1 submit提交单个任务
2 map通过迭代,提交多个任务
ThreadPoolExecutor
的用法在官方教程中示例为:
但需要注意的是 通过map
和submit
方法保存的list列表有区别,map可以直接遍历结果,用submit得到的是Future
对象,需要多一步
使用线程池一个例子:
终止线程
- 如果线程中有循环,终止循环,线程就可以停止
- 如果run方法有异常,就会退出
- 如果线程是守护线程,那么当主线程结束的时候,线程会停止
-
- 即如果线程是非守护线程,那么主线程结束的时候,Python程序会等到线程执行完毕,再结束
-
- 主线程创建线程,默认是非守护线程
- 主线程创建线程,默认是非守护线程
- Python没有提供强制终止一个线程的API,所以我们要把线程中任务设置成可终止
线程通信
- 变量(多个线程,使用的是同一块内存,全局变量是共享)
- 队列
-
- 先进先出FIFO
-
- 后进后出LIFO
-
- 优先级:数值越小,优先级越高
锁
- Lock互斥锁:同一时刻只能被一个线程获取
- RLock可重用锁:可以被多次获取,获取了多少次就应该释放多少次
- Semaphore信号量:同一时刻,可以被一些线程获取(在实例化时通过参数传递)、保护资源同时只被指定数量的线程操作
- Condition条件锁:
CPython解释器采用全局解释器锁GIL,确保同一时刻只有一个线程再执行Python字节码,不包括C代码 IO操作
锁的基本用法:
- 创建锁
lock = Lock()
- 加锁
lock.acquire()
- 释放锁
lock.release()
互斥锁用法示例:获取了多少次就应该释放多少次
可重用锁:
信号量:
在同一时间片段中,不会超过五个线程
死锁
- 对同一个锁,获取多次,从第二次开始阻塞,导致没有机会释放
- 香菇等待:获取A锁,等待B锁,但是B锁要在A释放之后才能释放B