GIL是什么?
首先我们要知道一个常识,python的解释器有很多种,我们用的是cpython解释器,Cpython是使用C语言编写的
那么GIL的本质就是一把互斥锁:
互斥锁:
将并发转成串行,牺牲了运行效率,但是提高了安全性,锁就是为了安全嘛
使用来阻止一个进程下多个线程的同时执行,(同一个进程中的多个线程是不能并行的,但是是可以实现并发)
GIL全局解释器锁的存在是因为cpython中内存管理线程是不安全的,因为垃圾回收机制,如果没有GIL那么会产生刚生成完的变量直接被垃圾回收机制的给回收了
那么垃圾回收机制是什么?
1.引用计数
内存中的数据如果没有任何的变量名与其有绑定关系,那么会被自动回收
2.标记清除
当内存快要被某个应用程序占满的时候,会自动触发
3.分代回收
根据值的存活时间的不同,划为不同的等级,等级越高垃圾回收机制扫描的频率越低
GIL的特点:
gil不是python解释器的特点,单进程边无法利用多核的优势这是所有解释性语言的通病
解释性语言是是读一行解决一行,这个和编译性的方法不一样,他不是python的优势,是cpython的
针对不同的数据我们应该加不同的锁进行处理,不能用一把锁使用到不同的数据上面,GIL是用来保证线程的安全,是不可在其他数据
上使用的,有了GIL后我们在进程中就不需要加其他的互斥锁
# 验证实例
from threading import Thread
import time
n = 100
def task():
global n
tmp = n
time.sleep(1)
# 因为第一个抢到锁后休息一秒钟,产生了IO操作,把锁自动释放让其他的人都去抢到后休息一秒钟
# 这个时候原来的100-1等于99,这个时候做有的人的手里面的值都是99
# 如果没有进行io操作,那么下一个抢到锁的人拿到的值就是上一个人处理后的结果
n = tmp - 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(t)
print(n)
这样说难道是python中多线程是不是也没法利用多核的优势了吗?
那这个就研究就一下了,我们从两个方面来举例
一. 四个任务 计算密集类型方面
单核情况下
开线程更省资源,因为开进程要开空间,进程则不需要开空间
多核情况下
开进程比开线程快4倍,开进程可以同时计算,但是线程只能一个一个的计算,是需要排队的
二. 四个任务 io密集类型方面
单核情况下
开线程节省资源
多核情况下
也是开线程更节省资源,因为他是io操作,和cpu多少核心是没有什么关系反正都要停滞,一个是等待一个是切换.
结论就是:
肯定是有用的,但是需要看情况而定,一般情况下呢都是多进程+多线程配合使用的
# 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())
start = time.time()
for i in range(300):
# p = Process(target=work) # 运行时间:16.447757959365845
p = Thread(target=work) # 运行时间:2.0386486053466797
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print("运行时间:%s" % (stop - start))
# 计算密集型
def work1():
res = 0
for i in range(100000000):
res *= i
if __name__ == '__main__':
l = []
print(os.cpu_count()) # 本机为8核
start = time.time()
for i in range(6):
p = Process(target=work1) # 耗时 运行时间:7.944072008132935
# p = Thread(target=work1) # 运行时间:29.27473521232605
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print("运行时间:%s" % (stop - start))
死锁和递归锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的
一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁
状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
递归锁
我们分析了死锁,那么python里面是如何解决这样的递归锁呢?
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,
从而使得资源可以被多次require。直到一个线程所有的acquire都被release,
其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
死锁实例:
from threading import Thread, Lock
import time
mutexA = Lock()
mutexB = Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('