查看所有Python相关学习笔记
多线程开发:
- 进程:
进程的概念:运行着的程序
每个进程至少包含一个线程
线程是操作系统创建的,用来控制代码执行的数据结构
线程就像代码的执行许可证
- 单线程程序,主线程的入口就是代码的开头
主线程顺序往下执行,直到所有的代码都执行完
- 多线程
- 一个服务窗口 = CPU的一个核
- 客户 = 进程(运行着的程序)
- 调度员 = 操作系统(dos)
- 服务号 = 线程
调度员分配服务号给客户 = OS分配线程给进程代码 服务窗口给客户办业务 = CPU核心执行线程代码
-
注意点:
- 服务窗口,在一个时间点上只能服务一个顾客业务
- CPU核心,在一个时间点上只能执行一个线程代码
-
调度的概念:
- 调度员分配窗口给客户
- 一个客户不一定占用一个窗口一直到它结束
- 比如需要很长时间填写表格
- 这时候可以让下一个客户来办理
- 先前的客户填好了表格,再继续
-操作系统不会让一个线程一直占用CPU的
进程里的多线程
- 线程库:代码通过系统调用,请求OS分配一个新的线程
- Python里面
- thread
- threading(重点)
- 都可以用来创建和管理线程
- thread 比较底层
- threading 是thread模块的扩展,提供了很多线程同步功能,使用起来更加方便强大
多线程的概念:
代码通过系统调用,请求OS分配一个新的线程,与原来的线程并行的执行一段代码
-
多线程给一个程序并行执行代码的能力:
- 同时处理多个程序
- 常见的:
- UI线程
- 任务线程 task exeute
join():哪个线程调用了join(),就等待该线程执行完,
print('main thread start')
import threading
from time import sleep #引入时间睡眠
def thread1_entry(nsec):
print('child thread 1,start')
sleep(15) #子线程停止15秒
print('child thread 1,end')
#无join()的情况下:因为主线程睡眠10秒,子线程睡眠15秒,
#所以10s后先执行主线程的print,15s后再执行子线程的此print
t1 = threading.Thread(target=thread1_entry,args=(15,))
#创建线程对象
#args:函数的参数,tuple型
t1.start() #启动子线程,此时主线程继续执行
sleep(10) #主线程停止10秒
t1.join()#等待t1线程结束
print('main thread end')
#执行结果
main thread start
child thread 1,start
child thread 1,end
main thread end
- 注意点:
多线程使用共享数据
- 共享对象的概念:
- 高铁上的厕所
- 某个时刻只能一个人使用
- 进入后往往立即锁门(表示已经使用)
- 看到的人,门口排队等待
- 用完开锁(表示已经使用完了)
- 排队的人中下一个去使用(重复这个过程)
- 有些资源是某个时刻独占使用的,如果不加锁
- 某人使用厕所
- 另一个人也进入使用
- 发生冲突
-
锁保证了
- 只有一个人去使用
- 别人必须等待
例子:
jcy用户在支付宝账号的余额为2000元
他乘坐滴滴打车要扣钱
他的余额宝会给他挣钱
处理滴滴打车扣钱逻辑在线程#1里执行,
处理余额宝挣钱的逻辑在线程#2里执行
今天,他坐滴滴扣了10元,而余额宝挣的钱也是10元
--> 线程#1和线程#2同时调用余额,造成冲突。所以要加锁
--> 一个先调,执行完后,另一个再调
加锁:
- 注意锁的代码位置
- 在访问共享对象的代码前,要调用Lock对象的acquire方法,进行上锁操作。当多个线程同时执行lock.acquire()时,只有一个线程能成功的取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
- 访问结束后,一定要调用Lock对象的release方法,进行解锁操作。否则其他等待锁的线程将永远等待下去,成为死线程。
- 加锁是原子操作,不会有同时加锁的情况。
#调用Lock函数,返回一个锁对象
zhifubao_lock = threading.Lock()
#在代码访问共享对象之前,加锁
#当多个线程同时执行lock.acquire()时,
#只有一个线程能成功的获取锁,然后继续执行代码
#其他线程就继续等待,直到获得锁为止。
zhifubao_lock.acquire()
#访问完共享对象 释放锁
#访问结束后,一定要调用lock对象的release方法,进行解锁操作
#否则其他等待锁的线程将永远等待下去,成为死进程
zhifubao_loce.release()
条件变量:
- 生产者、消费者:
- 一个线程负责让用户输入命令,存入一个List中
- 另一个线程负责从List中取出命令,执行命令
- 用户输入命令的速度,和执行命令的速度,谁快谁慢,还很难说
- 负责让用户输入命令的线程:生产者,产生命令存入列表中
- 负责执行命令的线程:消费者,取出列表中的命令
- 采用锁来解决?
- 条件变量的使用:
- 线程A(消费者)通过条件变量对象等待一个条件满足,否则就睡眠式等待
- 线程B(生产者)在条件满足时,通过条件变量通知 唤醒线程A
- 线程A(消费者)接到通知,从睡眠中醒来,继续代码的执行
#调用Condition,返回一个条件对象,该对象包含了一个锁对象
cv = threading.Condition()
#先申请锁,条件变量包含了锁,可以使用acquire
cv.acquire()
#如果命令为空,调用wait方法,该调用会释放锁,并且阻塞在此处
#直到生产者生产出资源后,调用该条件变量的notify,唤醒自己
#一旦被唤醒,将重新获取锁(所有生产者线程此时不能对共享资源进行操作)
while commandList == []: #commandList是自定义的一个列表
cv.wait()
#申请锁并操作后,随后调用notify,就像说有任务啦,等任务的线程来处理吧
#该调用会唤醒一个 阻塞在该条件变量上等待的消费者线程
cv.notify()
#当然也要释放一下生产者里面的锁
cv.release
其他常用线程同步技术:
- RLock - 可重入锁
- Semaphores - 信号量