05线程2 GIL与Lock等
一、GIL(Global Interpreter Lock:全局解释器锁)
每次执行python程序,都会产生一个独立的进程。在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内。
某个线程想要执行,必须先拿到 GIL,我们可以把 GIL 看作是“通行证”,并且在一个 Python 进程中,GIL 只有一个。
拿不到通行证的线程,就不允许进入 CPU 执行。
全局解释锁(GIL):就是一个互斥体,只允许一个线程来控制Python解释器。
二、 GIL与Lock
GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理
需要注意的点:
1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,
其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来
2.join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,
要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高
例:多线程共享数据没有加锁
from threading import Thread import time def task(): global n temp = n time.sleep(0.1) n = temp - 1 if __name__ == '__main__': n = 100 l = [] for i in range(100): t = Thread(target=task,) l.append(t) t.start() for t in l: t.join() print('主', n) #结果为99,因为创建线程速度很快,数据没有加锁,每个线程拿到的n都是100
加锁解决数据共享问题,方式一
from threading import Thread,Lock import time def task(): global n lock.acquire() #加锁,也可以用这种方式:with mutex:#加锁 temp = n time.sleep(0.1) n = temp - 1 lock.release() #释放锁 if __name__ == '__main__': lock = Lock() n = 100 l = [] for i in range(100): t = Thread(target=task) l.append(t) t.start() for t in l: t.join() print('主', n) #结果为0
方式二:这种方式:with mutex:#加锁
from threading import Thread, Lock import time n = 100 def task(): global n with mutex:#加锁,因为线程共用的一个进程的资源,因此,用的都是同一把锁,不用把锁作为参数传进去了 temp = n time.sleep(0.1) n = temp -1 if __name__ == '__main__': mutex = Lock() t_l = [] for i in range(100): t = Thread(target=task) t_l.append(t) t.start() for t in t_l: t.join()#保证所有的子线程都执行完毕 print('主', n)
三、死锁与递归锁
死锁:指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,
若无外力作用,它们都将无法推进下去。
解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁
from threading import Thread, Lock, RLock import time # mutexA = Lock() # mutexB = Lock() mutexA = mutexB = RLock() class MyThead(Thread): def run(self): self.fun1() self.fun2() def fun1(self): mutexA.acquire() print('{}拿到A锁'.format(self.name)) mutexB.acquire() print('{}拿到B锁'.format(self.name)) mutexB.release() mutexA.release() def fun2(self): mutexB.acquire() print('{}拿到B锁'.format(self.name)) time.sleep(1) mutexA.acquire() print('{}拿到A锁'.format(self.name)) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t = MyThead() t.start()
四、信号量
from threading import Thread, Semaphore, current_thread import time, random sm = Semaphore(5) #信号量设为5,同时最大连接数为5,同时只有5个线程可以得到Semaphore def task(): with sm: print('{} is running'.format(current_thread().getName())) time.sleep(random.randint(1,3)) print('{} is done'.format(current_thread().getName())) if __name__ == '__main__': for i in range(20): t = Thread(target=task) t.start()
五、Event
对象包含一个可由线程设置的信号标志, 它允许线程等待某些事件的发生。
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
例1:
# (1)最简单的版本 from threading import Thread, Event, current_thread import time e = Event() def check(): print('{} is checking'.format(current_thread().getName())) time.sleep(3) e.set() def conn(): print('{} is wait to connect Mysql'.format(current_thread().getName())) e.wait() print('{} connected Mysql'.format(current_thread().getName())) if __name__ == '__main__': t1 = Thread(target=check) t2 = Thread(target=conn) t3 = Thread(target=conn,) t4 = Thread(target=conn,) t1.start() t2.start() t3.start() t4.start() # (2)设置超时时间,最大连接次数 from threading import Thread, Event, current_thread import time e = Event() def check(): print('{} is checking'.format(current_thread().getName())) time.sleep(0.1) e.set() def conn(): count = 1 #技术,连接次数 while not e.is_set(): if count > 3: #如果三次连接失败,则超时 raise TimeoutError('超时') print('{} is wait to connect Mysql'.format(current_thread().getName())) e.wait(1) # 等待时间 count += 1 print('{} connected Mysql'.format(current_thread().getName())) if __name__ == '__main__': t1 = Thread(target=check) t2 = Thread(target=conn) t3 = Thread(target=conn,) t4 = Thread(target=conn,) t1.start() t2.start() t3.start() t4.start() # (3)设置超时时间,最大连接次数,循环的另一个写法 from threading import Thread, Event, current_thread import time e = Event() def check(): print('{} is checking'.format(current_thread().getName())) # time.sleep(0.0001) e.set() def conn(): count = 1#计数,连接次数 while count < 4: print('{} is wait to connect Mysql'.format(current_thread().getName()))#这两句必须要放前面 e.wait(1) # 等待时间 if e.is_set():#此时e为TRUE,说明可以连接 print('{} connected Mysql'.format(current_thread().getName())) break count += 1 else: raise TimeoutError('超时') if __name__ == '__main__': t1 = Thread(target=check) t2 = Thread(target=conn) t3 = Thread(target=conn,) t4 = Thread(target=conn,) t1.start() t2.start() t3.start() t4.start()
例2:红绿灯
import time, random from threading import Thread, Event, current_thread e = Event() def f1(): while True: e.clear() print('红灯,等2秒') time.sleep(2) e.set() print('绿灯,持续2秒') time.sleep(2) def f2(): while True: if e.is_set(): print('{} 过马路'.format(current_thread().getName())) break else: print('{}等待'.format(current_thread().getName())) e.wait() if __name__ == '__main__': Thread(target=f1).start() while True: time.sleep(random.randint(1,5)) Thread(target=f2).start()
六、定时器:指定n秒后执行
from threading import Timer def hello(name): print('hello {}'.format(name)) t = Timer(1, hello, args=('cc',)) #指定1秒后执行 t.start()
七、线程Queue
queue队列 :使用import queue,用法与进程Queue一样
# (1)q=queue.Queue()队列:先进先出 import queue q = queue.Queue() q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) # (2)q = queue.LifoQueue(),堆栈,后进先出 import queue q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) # (3)q = queue.PriorityQueue(),优先级队列 import queue q = queue.PriorityQueue() q.put((20, 1)) q.put((30, 2)) q.put((10, 3)) print(q.get()) print(q.get()) print(q.get())
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】