GIL全局解释器锁

【一】GIL全局解释器锁

明确一点:从多线程数据共享,数据可以被更改线程数次,可以看出存在GIL全局解释器锁

"""纯理论 不影响编程 只不过面试的时候可能会被问到"""
# 官方文档
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. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
"""
1.回顾
    python解释器的类别有很多
        Cpython Jpython Ppython
    垃圾回收机制
        应用计数、标记清除、分代回收
    
GIL只存在于CPython解释器中,不是python的特征
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行
原因是因为CPython解释器中的垃圾回收机制不是线程安全的
​
反向验证GIL的存在 如果不存在会产生垃圾回收机制与正常线程之间数据错乱
GIL是加在CPython解释器上面的互斥锁
同一个进程下的多个线程要想执行必须先抢GIL锁 所以同一个进程下多个线程肯定不能同时运行 即无法利用多核优势
​
​
强调:同一个进程下的多个线程不能同时执行即不能利用多核优势
    很多不懂python的程序员会喷python是垃圾 速度太慢 有多核都不能用
​
反怼:虽然用一个进程下的多个线程不能利用多核优势 但是还可以开设多进程!!!
​
再次强调:python的多线程就是垃圾!!!
​
反怼:要结合实际情况 
    如果多个任务都是IO密集型的 那么多线程更有优势(消耗的资源更少)
        多道技术:切换+保存状态
    如果多个任务都是计算密集型 那么多线程确实没有优势 但是可以用多进程
        CPU越多越好
    
以后用python就可以多进程下面开设多线程从而达到效率最大化
"""
1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
2.GIL在实际编程中其实不用考虑
 

【二】GIL与普通互斥锁区别

1.先验证GIL的存在
谁先抢到谁就先处理数据
from threading import Thread, Lock
import time
  money = 100
  def task():
      global money
      money -= 1#GIL加在这里
  for i in range(100):  # 创建一百个线程
      t = Thread(target=task)
      t.start()
  print(money)#0,由于GIL的存在,一次只能一个线程获得锁
2.当睡了 0.1s 后---》所有线程都拿到了money=100之后,才去都去抢那把 GIL 锁住的数据,当所有子线程都抢到后再去修改数据就变成了 99
from threading import Thread, Lock
money = 100
def task():
    global money
    temp = money
    time.sleep(0.1)
    money -= temp
def main():
    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(money)
if __name__ == '__main__':
    main()
    # 99
3.要改变睡眠了之后被同时取数据,产生数据错乱
自动加锁并解锁
子线程启动 , 后先去抢 GIL 锁 , 进入 IO 自动释放 GIL 锁 , 但是自己加的锁还没解开 ,其他线程资源能抢到 GIL 锁,但是抢不到互斥锁
最终 GIL 回到 互斥锁的那个进程上,处理数据
from threading import Thread, Lock
import time
money = 100
mutex = Lock()
def task():
    global money
    mutex.acquire()
    tmp = money
    time.sleep(0.1)
    money = tmp - 1
    mutex.release()
    """
      抢锁放锁也有简便写法(with上下文管理)
      with mutex:
          pass
      """
    t_list = []
    for i in range(100):  # 创建一百个线程
        t = Thread(target=task)
        t.start()
        t_list.append(t)
        for t in t_list:
            t.join()
            # 为了确保结构正确 应该等待所有的线程运行完毕再打印money
            print(money)
【三】递归锁
可以被连续的 acquire 和 release
​
但是只能被第一个抢到这把锁上执行上述操作
​
他的内部有一个计数器,每acquire一次计数 +1 每release一次 计数-1
​
只要计数不为0,那么其他人都无法抢到该锁
from threading import Thread, Lock, RLock
import time
拿了就放
# 两个变量同时指向一把锁
metexA = metexB = RLock()
# 类只要加括号多次 产生的肯定不同的对象
# 如果你想要实现多次加括号等到的是相同的对象 - 单例模式
class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()
​
    def func1(self):
        metexA.acquire()
        # self.name:获取当前线程名
        print(f'{self.name} 抢到了A锁')
        metexB.acquire()
        print(f'{self.name} 抢到了B锁')
        metexB.release()
        metexA.release()
​
    def func2(self):
        metexB.acquire()
        # self.name:获取当前线程名
        print(f'{self.name} 抢到了A锁')
        time.sleep(2)
        metexA.acquire()
        print(f'{self.name} 抢到了B锁')
        metexA.release()
        metexB.release()
​
​
def main():
    for i in range(10):
        t = MyThread()
        t.start()
​
​
if __name__ == '__main__':
    main()
​
    # Thread-1 抢到了A锁
    # Thread-1 抢到了B锁
    # Thread-1 抢到了A锁
    # Thread-1 抢到了B锁
    # Thread-2 抢到了A锁
    # Thread-2 抢到了B锁
    # Thread-2 抢到了A锁
    # Thread-2 抢到了B锁
    # Thread-4 抢到了A锁
    # Thread-4 抢到了B锁
    # Thread-4 抢到了A锁
    # 不会卡主正常进行
【四】死锁
  • 死锁是指两个或多个进程,在执行过程中,因争夺资源而造成了互相等待的一种现象。
    
    from threading import Thread, Lock
    import time
    ​
    metexA = Lock()
    metexB = Lock()
    ​
    ​
    # 类只要加括号多次 产生的肯定不同的对象
    # 如果你想要实现多次加括号等到的是相同的对象 - 单例模式
    ​
    class MyThread(Thread):
        def run(self):
            self.func1()
            self.func2()
    ​
        def func1(self):
            metexA.acquire()
            # self.name:获取当前线程名
            print(f'{self.name} 抢到了A锁')
            metexB.acquire()
            print(f'{self.name} 抢到了B锁')
            metexB.release()
            metexA.release()
    ​
        def func2(self):
            metexB.acquire()
            # self.name:获取当前线程名
            print(f'{self.name} 抢到了A锁')
            time.sleep(2)#阻塞了线程2就开始了,拿了A,要B,但是B已经被1拿走了,1需要A,但是已经被2拿走了,两个线程僵持
            metexA.acquire()
            print(f'{self.name} 抢到了B锁')
            metexA.release()
            metexB.release()
    ​
    ​
    def main():
        for i in range(10):
            t = MyThread()
            t.start()
    ​
    ​
    if __name__ == '__main__':
        main()
        
        # Thread-1 抢到了A锁
        # Thread-1 抢到了B锁
        # Thread-1 抢到了A锁
        # Thread-2 抢到了A锁
        # 线程卡死
        # 开启十个线程 第一个线程走完第一圈 回到原地抢 A 结果第二个线程已经拿到了A 导致AB卡死

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值