一、GIL的全称是Global Interpreter Lock(全局解释器锁)
在Python多线程下,每个线程的执行方式:
(1)获取GIL
(2)执行代码直到sleep或者是Python虚拟机将其挂起
(3)释放GIL
注意:
(1)Python中一个线程对应于c语言中的一个线程,gil使得同一时刻只有一个线程在一个CPU上执行字节码,无法将多个线程映射到多个CPU上执行;
(2)GIL会根据执行的字节码行数以及时间片释放GIL,GIL在遇到IO的操作时候主动释放。
二、python多线程介绍
(1) 多线程threading模块
方法一 :创建threading.Thread类的实例,调用其start()方法
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
在线程里,传递参数有三种方法
a.使用元组传递 threading.Thread(target=方法名,args=(参数1,参数2, …)
b.使用字典传递 threading.Thread(target=方法名, kwargs={“参数名”: 参数1, “参数名”: 参数2, …})
c.混合使用元组和字典 threading.Thread(target=方法名,args=(参数1, 参数2, …), kwargs={“参数名”: 参数1,“参数名”: 参数2, …})
例题:
模仿多线程用户积分兑换盒子过程:
99个积分兑换1个盒子
已兑换过的不能再兑
import threading
from time import sleep
class User():
def __init__(self, user_id, point, flag):
self.user_id = user_id
self.point = point
self.flag = flag
box = 500000
lock = threading.Lock()
def order(user):
global box #global修饰,函数内才可使用全局变量
lock.acquire()
print(threading.current_thread())
if box >= 0 and user.point >= 99 and user.flag == 0:
box -= 1
user.point -= 99
user.flag = 1
lock.release()
print("userid:{},userpoint:{},userflag:{}".format(user.user_id, user.point, user.flag))
print("box:{}".format(box))
sleep(0.1)
u1 = User(1, 104, 0)
u2 = User(2, 206, 0)
#注意输入的args为元组类型,单个元素时需要,结尾
t1 = threading.Thread(name="t1", target=order, args=(u1,))
t2 = threading.Thread(name="t2", target=order, args=(u2,))
t1.start()
t2.start()
t1.join()
t2.join()
方法二:继承Thread类,在子类中重写run()和init()方法
Thread类提供了以下方法:
run(): 用以表示线程活动的方法。
start():启动线程活动。
join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选 的超时发生。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
threading.Lock:它是一个基本的锁对象,每次只能锁定一次,其余的锁请求,需等待锁释放后才能获取。
threading.RLock:它代表可重入锁(Reentrant Lock)。对于可重入锁,在同一个线程中可以对它进行多次锁定,也可以多次释放。
三、线程间通信-Queue
Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。
这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。
Queue 模块中的常用方法:
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.full 与 maxsize 大小对应
Queue.get([block[, timeout]])获取队列,timeout等待时间
Queue.get_nowait() 相当Queue.get(False)
Queue.put(item) 写入队列,timeout等待时间
Queue.put_nowait(item) 相当Queue.put(item, False)
Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
Queue.join() 实际上意味着等到队列为空,再执行别的操作
*Queue可以用来存储线程的处理结果
import time
import threading
from queue import Queue
四、concurrent.futures线程池
Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码,但是当项目达到一定的规模,频繁创建/销毁进程或者线程是非常消耗资源的,这个时候我们就要编写自己的线程池/进程池,以空间换时间。
但从Python3.2开始,标准库为我们提供了concurrent.futures模块,它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,实现了对threading和multiprocessing的进一步抽象,对编写线程池/进程池提供了直接的支持。
Executor和Future:
concurrent.futures模块的基础是Exectuor,Executor是一个抽象类,它不能被直接使用。但是它提供的两个子类ThreadPoolExecutor和ProcessPoolExecutor却是非常有用,两者分别被用来创建线程池和进程池的代码。可以将相应的tasks直接放入线程池/进程池,不需要维护Queue来操心死锁的问题,线程池/进程池会自动帮我们调度。
Future可以把它理解为一个在未来完成的操作,这是异步编程的基础,传统编程模式下比如我们操作queue.get的时候,在等待返回结果之前会产生阻塞,cpu不能让出来做其他事情,而Future的引入帮助我们在等待的这段时间可以完成其他的操作