目录
GIL全局解释器
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
要想了解GIL,首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生不同的python进程
基于Cpython来研究全局解释锁
1.GIL本质上是一个互斥锁
2.GIL是为了阻止同一个进程内多个线程同时执行(并行)
单个进程下的多个线程无法实现并行,但能实现并发
3.这把锁主要是因为CPython的内存管理不是“线程安全”的
内存管理
垃圾回收机制
GIL的存放就是为了保证线程安全的
注意:多线程过来执行,一旦遇到IO操作,就会立马释放GIL解释器,交给下一个先进来的进程
import time
from threading import Thread,current_thread
number = 100
def task():
global number
number2 = number
number = number2 - 1
print(number,current_thread().name)
for line in range(100):
t = Thread(target=task)
t.start()
验证多线程的作用
多线程的作用:
站在两个角度去看问题:
四个任务,计算密集型,每个任务需要10s
单核:
开启进程
4个进程:40s
开启线程
消耗资源远小于进程
4个线程:40s
多核:
开启进程
并行执行,效率比较高
4个进程:10s
开启线程
并发执行,执行效率低
4个线程:40s
四个任务,IO密集型,每个任务需要10s
单核:
开启进程
消耗资源过大
4个进程:40s
开启线程
消耗资源远小于进程
4个线程:40s
多核:
开启进程
并行执行,效率小于多线程,因为遇到IO立马切换CPU的执行权限
4个进程:40s+开启进程消耗的额外时间
4个线程:40s
在计算密集的情况下:
使用多进程
在IO密集型的情况下:
使用多线程
高效执行多进程内多个IO密集型的程序:
使用 多进程+多线程
应用:
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密度型,如金融分析
死锁现象
所谓死锁:指的是两个或两个以上的进程或线程在执行过程中,因为夺资源而造成的一种互相等待的现象,若无外力作用,它们都无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永久在互相等待的进程称为死锁进程。
from threading import Lock as Lock
import time
mutexA = Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()
解决死锁的方法:递归,在python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护这一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果用RLock,则不会发生死锁。
典型问题:科学家吃面
死锁问题
#死锁的问题
import time
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
noodle_lock.acquire()
print('%s 抢到了面条'%name)
fork_lock.acquire()
print('%s 抢到了叉子'%name)
print('%s 吃面'%name)
fork_lock.release()
noodle_lock.release()
def eat2(name):
fork_lock.acquire()
print('%s 抢到了叉子' % name)
time.sleep(1)
noodle_lock.acquire()
print('%s 抢到了面条' % name)
print('%s 吃面' % name)
noodle_lock.release()
fork_lock.release()
for name in ['哪吒','nick','tank']:
t1 = Thread(target=eat1,args=(name,))
t2 = Thread(target=eat2,args=(name,))
t1.start()
t2.start()
递归锁解决死锁问题
import time
from threading import Thread,RLock
fork_lock = noodle_lock = RLock()
def eat1(name):
noodle_lock.acquire()
print('%s 抢到了面条'%name)
fork_lock.acquire()
print('%s 抢到了叉子'%name)
print('%s 吃面'%name)
fork_lock.release()
noodle_lock.release()
def eat2(name):
fork_lock.acquire()
print('%s 抢到了叉子' % name)
time.sleep(1)
noodle_lock.acquire()
print('%s 抢到了面条' % name)
print('%s 吃面' % name)
noodle_lock.release()
fork_lock.release()
for name in ['哪吒','nick','tank']:
t1 = Thread(target=eat1,args=(name,))
t2 = Thread(target=eat2,args=(name,))
t1.start()
t2.start()
递归锁
递归锁(了解)
用于解决死锁问题
RLock:比喻成万能钥匙,可以提供给多个人去使用
但是第一个使用的时候,会对该锁做一个引用计数
只有引用计数为0时,才能真正释放让另一个去使用
from threading import RLock,Thread,Lock
import time
mutex_a = mutex_b = Lock()
class MyThread(Thread):
#线程执行任务
def run(self):
self.func1()
self.func2()
def func1(self):
mutex_a.acquire()
print(f'用户{self.name}抢到了锁a')
mutex_b.acquire()
print(f'用户{self.name}抢到了锁b')
mutex_b.release()
print(f'用户{self.name}释放锁b')
mutex_a.release()
print(f'用户{self.name}释放锁a')
def func2(self):
mutex_b.acquire()
print(f'用户{self.name}抢到锁b')
#IO操作
time.sleep(1)
mutex_a.acquire()
print(f'用户{self.name}抢到锁a')
mutex_a.release()
print(f'用户{self.name}释放锁a')
mutex_b.release()
print(f'用户{self.name}释放锁b')
for line in range(10):
t= MyThread()
t.start()
信号量
信号量(了解):
互斥锁:比喻成一个家用马桶
同一时间只能让一个人去使用
信号量:比喻成公厕多个马桶,
同一时间可以让多个人去使用
from threading import Semaphore,Lock
from threading import current_thread
from threading import Thread
import time
sm = Semaphore(5)
mutex = Lock()
def task():
sm.acquire()
print(f'{current_thread().name}执行任务')
time.sleep(1)
sm.release()
for line in range(20):
t= Thread(target=task)
t.start()
线程队列
线程Q(了解级别1):线程队列 面试会问:FIFO
FIFO队列:先进先出
LIFO队列:后进先出
优先级队列:根据参数内,数字的大小进行分级,数字值越小,优先级越高
import queue
普通的线程队列:先进先出
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get()) #1
LIFO队列:后进先出
q = queue.LiFoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())#3
优先级队列
q=queue.PriorityQueue()
#若参数中传的是元组,会以元组中第一个数字参数为准
q.put(('a优','娃娃头',4))#a==97 ascll码值
q.put(('b优','娃娃头',4))#b==98
1.首先根据第一个参数判断ascll表的数值大小
2.判断第几个参数中的汉字顺序
3.在判断第二个参数中的数字————》字符串数字---》中文
4.以此类推