多线程基本介绍
有很多场景中的事是同时进行的,比如开车的时候手和脚共同来驾驶汽车,再比如唱歌跳舞同时进行
如何创建线程
· 方法一:通过函数
使用 threading 模块中的Thread类, 在这个类中有一个target参数需要传递一个函数对象。而这个函数内,就是多线程的逻辑
· 方法二:通过类的方式
创建一个类,当时这个类需要基础threading.Thread。并实现(重写) run()方法。在 run() 方法中实现多线程的逻辑
主线程和子线程的执行优先级
· 主线程会等待子线程结束之后再结束
· join(),等待子线程全部结束后,主线程序才继续执行
· setDaemon() 守护线程,不会等待子线程结束,主线程先行结束
程序中模拟多任务
验证子线程的执行与创建
当调用Thread的时候,不会去创建线程
当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及开始运行这个线程
enumerate()用来查看当前活动线程的数量
线程间共享全局变量
在一个函数中,对全局变量进行修改的时候,是否要加global要看是否对全局变量的指向进行了修改,如果修改了指向,那么必须使用global,仅仅是修改了指向的空间中的数据,此时不用必须使用global, 线程是共享全局变量的
线程间的资源竞争
一个线程写入,一个线程读取,如果两个线程都写入呢?
互斥锁和死锁
· 互斥锁
当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其它线程不能改变。
只到该线程释放资源,将资源的状态变成“非锁定”,其它的线程才能再次锁定该资源。
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
创建锁
mutex = threading.Lock()
锁定
mutex. acquire()
解锁
mutex.release()
· 死 锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁
· 通过Threading.Lock()创建的锁是不可重复的锁
也就是mutex.acquire() 与mutex.release()是成对出现的。
不能出现 mutex.acquire() 后又重复跟了一个 mutex.acquire()
· 避免死锁
· 程序设计时要尽量避免
· 添加超时时间
Queue 线程
在线程中,访问一些全局变量,加锁是一个经常的过程。如果是想把一些数据存储到某个队列中,那么python内置了一个线程安全的模块叫做queue模块。
python 中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(进先出)队列)队列Queue,
LIFO( 先入后出 )队列LifoQueue
这些队列都实现 了锁原语(可以理解为原子操作,要么不做,要么都做完),能够在多线程中直接使用。可以使用队列来实现线程间的异步
生产者和消费者
生产者和消费者模式是多线程开发中常见的一种模式。通过生产者和消费者模式,可以让代码达到高内聚低耦合的目标,线程管理更加方便,程序分工更加明确。
生产者的线程专门用来生产一些数据,然后存放到容器中(中间变量)。消费者在从这个中间的容器中取出数据进行消费
Lock版生产者与消费者示例:
Condition版的生产者和消费者示例: