线程二
一:GIL(全局解释器锁)#
(1)官方解释:

''' In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe '''
PS:
(1)python解释器有很多 但是常见的就是Cpython解释器
(2)GIL本质也是一把锁------->将程序运行由并发变成串行 提高数据的安全性 牺牲了执行效率
(3)GIL的存在是因为Cpython解释器的内存管理不是线程安全的
PS:图解上述多线程不安全的原因
(1)首先python代码想要执行 需要被解释器翻译成二进制 传给计算机
(2)假设没有GIL锁的情况下 即所有的线程都可以同时使用解释器进行翻译
(3)在所有线程都可以使用解释器的情况下
(4)假设我此时定义一个变量 即将调用该变量
(5)但是此时垃圾回收机制触发 将还没有被调用的变量给清除 那么我变量便不能被调用了
(6)因此需要使用GIL锁对解释器进行加锁处理
PS:
(1)CIL不是python的特点 是Cpython的特点
(2)单进程下无法使用多核优势是解释型语言的通病
验证GIL与普通的互斥锁
例如:

from threading import Thread import time n = 100 def run(): global n temp = n # 模拟网络延迟 time.sleep(1) n = temp - 1 t_list = [] for i in range(100): t = Thread(target=run) t.start() t_list.append(t) for t in t_list: t.join() print(n) # 99
PS:
(1)首先上述结果不是为0 而是为99
(2)因为在上述中CPU沉睡1S 导致CPU释放 然后所有的线程都卡在sleep中
(3)在上述sleep之前所有的线程都拿到变量100 当执行完毕之后都进行减1
总结:GIL在多线程的情况下 不会进行并行 只会进行并发
(2)Python多核情况下多线程是否有用
(1)计算密集型
单核情况:
(1)开多线程:
PS:在计算型情况下CPU在进行大量的运算 开多进程占用大量的资源 开多线程节省资源
多核情况:
(1)开多进程:
PS:在多核情况下 如果处于多线程情况下CPU处于来回切换状态 效率低 而多进程 CPU可以处理多个进程下的多个线程
(2)I/O密集型
(1)单核情况下
(1)开多线程:
PS:在I/O密集型情况下 程序会处于堵塞状态 开多进程浪费资源
(2)多核情况下
(1)开多线程
PS:在I/O密集型情况下 程序会处于堵塞状态 开多进程浪费资源
代码示例:

# 计算密集型 from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) # 本机为4核 start=time.time() for i in range(6): # p=Process(target=work) #耗时 4.732933044433594 p=Thread(target=work) #耗时 22.83087730407715 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))

# IO密集型 from multiprocessing import Process from threading import Thread import threading import os, time def work(): time.sleep(2) if __name__ == '__main__': l = [] print(os.cpu_count()) # 本机为6核 start = time.time() for i in range(4000): p = Process(target=work) # 耗时9.001083612442017s多,大部分时间耗费在创建进程上 # p=Thread(target=work) #耗时2.051966667175293s多 l.append(p) p.start() for p in l: p.join() stop = time.time() print('run time is %s' % (stop - start))
PS:python多核多线程是否无用 需要看情况
二:死锁 + 递归锁#
死锁:两个或两个以上的线程/进程 两者相互持有对方所需要的锁且该锁不能被释放 造成互相等待的情况
例如:

from threading import Thread,Lock import time mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() print('%s抢到了A锁' % self.name) mutexA.release() print('%s释放了A锁' % self.name) mutexB.release() print('%s释放了B锁' % self.name) for i in range(10): t = MyThread()
PS:
(1)假如线程A和B
(2)首先线程A执行func1此时抢到A和B两个锁 并且释放掉
(3)当A释放A和B两锁的时候 B抢到A锁并且要抢B锁
(4)当A释放完A和B两锁的时候 开始执行func2 此时A抢到了B锁 沉睡1S需要抢A锁
(5)此时A手里拿着B锁同时需要A锁 B手里拿着A锁同时需要B锁 就造成所需要的资源在对方手里 且两者都不能主动释放 造成死锁现象
递归锁:
作用:解决上述死锁的问题
解决的办法:
(1)当有线程抢到该锁的时候
(2)每acquire一次锁的计数+1
(3)每release一次锁的计数-1
例如:

import time from threading import RLock,Thread mutexA = mutexB = RLock() class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() print('%s抢到了A锁' % self.name) mutexA.release() print('%s释放了A锁' % self.name) mutexB.release() print('%s释放了B锁' % self.name) for i in range(10): t = MyThread() t.start()
PS:
(1)在上述中假设线程A在函数1中先拿到锁 经历两次计数两次释放此时计数为空
PS:不需要考虑当线程A释放锁的时候别的线程能拿到该锁底层帮我们处理这个问题了
(2)接着开始走函数2此时拿到锁B计数为1 接着拿到锁A计数为2 释放两次其余线程拿到锁
三:信号量#
作用:可以允许多个线程/进程使用锁
PS:互斥锁(上厕所一个坑位)
信号量(上厕所多个坑位)
例如:

from threading import Semaphore,Thread import time import random sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 def task(name): sm.acquire() print('%s占了一个坑位'%name) time.sleep(random.randint(1,3)) sm.release() for i in range(40): t = Thread(target=task,args=(i,)) t.start()
四:Event事件#
作用:子线程需要等待某个子线程结束之后才能开始执行
例如:

from threading import Event,Thread import time # 先生成一个event对象 e = Event() def light(): print('红灯正亮着') time.sleep(3) e.set() # 发信号 print('绿灯亮了') def car(name): print('%s正在等红灯'%name) e.wait() # 等待信号 print('%s加油门飙车了'%name) t = Thread(target=light) t.start() for i in range(10): t = Thread(target=car,args=('伞兵%s'%i,)) t.start()
五:线程队列#
作用:
(1)队列是由管道 + 锁组成的 自带锁不需要我们手工操作锁
(2)防止造成死锁现象
例如:
import queue q = queue.Queue() q.put('hahha') print(q.get())
后进先出队列:
import queue q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get())
优先级队列:
作用:
(1)优先级比较高 取出来的概率比较大
(2)优先级数值越小 优先级越高
(3)存放数据以元组形式存放 第一个参数是优先级 第二个参数是传的数据
q = queue.PriorityQueue() # 数字越小 优先级越高 q.put((10,'haha')) q.put((100,'hehehe')) q.put((0,'xxxx')) q.put((-10,'yyyy')) print(q.get())
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!