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.)
View Code

Python解释器有很多种,最常见的Python解释器就是Cpython解释器

GIL(global interpreter lock) :本质是一把互斥锁,把并发变成串行牺牲效率来保证数据的安全性

在Cpython解释器中,同一个进程开启多线程,同一时刻只能有一个线程执行,无法利用多核优势。(只能并发,无法并行)

GIL存在的原因:是因为CPython解释器的内存管理线程是不安全的

GIL作用:保证python解释器同一时间只能执行一个线程

 

GIL不是python的特点,是Cpython解释器的特点

ps :单进程下多线程无法利用多核优势是所有解释型语言的通病。

 

多线程与多进程在计算密集型情况下的比较

GIL全局解释锁.死锁与递归锁
from multiprocessing import Process
from threading import Thread
import os,time

def work() :
    res = 0
    for i in range(10000000):
        res *= i

if __name__ == '__main__':
    l = []
    print(os.cpu_count())
    start = time.time()
    for i in range(4) :
        # p = Process(target= work) # run time is 2.1351988315582275
        p = Thread(target= work) # run time is 3.096200942993164
        l.append(p)
        p.start()

    for p in l :
        p.join()
    stop = time.time()
    print('run time is %s'%(stop - start))

# 单核情况下
#     开线程更省资源
# 多核情况下
#     开进程更快
View Code

 

多线程与多进程在I/O密集型情况下的比较

GIL全局解释锁.死锁与递归锁
from multiprocessing import Process
from threading import Thread
import os,time

def work() :
    time.sleep(2)

if __name__ == '__main__':
    l = []
    print(os.cpu_count())
    start = time.time()
    for i in range(4000) :
        # p = Process(target= work) # run time is 284.4450669288635
        p = Thread(target= work) # run time is 2.799881935119629
        l.append(p)
        p.start()

    for p in l :
        p.join()
    stop = time.time()
    print('run time is %s'%(stop - start))
# 单核情况下
#     开线程更节省资源
# 多核情况下
#     开线程更节省资源,也更快
View Code

 

GIL与普通的互斥锁:

GIL全局解释锁.死锁与递归锁
from threading import Thread
import time

n = 100


def task():
    global n
    tmp = n
    time.sleep(1)
    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(n)
View Code

针对不同的数据应该加不同的锁进行处理

GIL锁只是用来保证解释器级的数据,就是保证线程的安全

保护用户自己的数据则需要自己加锁处理

 

二.死锁与递归锁

死锁:两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,

   若无外力作用,他们将无法推进下去。

GIL全局解释锁.死锁与递归锁
from threading import Thread,Lock,current_thread

import time

mutexA = Lock()
mutexB = Lock()

class MyThread(Thread) :

    def run(self):
        print('***',current_thread().name,'***')
        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,20) :
    t = MyThread()
    t.start()
View Code

 

递归锁:在在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

GIL全局解释锁.死锁与递归锁
from threading import Thread,Lock,current_thread,RLock

import time

"""
Rlock可以被同一个执行线程连续的acquire和release
每acquire一次锁counter加1
每release一次锁counter减1
只要锁的计数不为0 其线程都必须等待
Rlock与Lock相比多了一个counter变量
counter记录了acquire的次数
"""

mutexA = mutexB = RLock()


class MyThread(Thread) :

    def run(self):
        # print('***',current_thread().name,'***')
        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,20) :
    t = MyThread()
    t.start()
View Code

 

三.信号量

信号量 :同时允许一定数量的线程修改资源

GIL全局解释锁.死锁与递归锁
"""
互斥锁:同时只允许一个线程更改数据
信号量:同时允许一定数量的线程更改数据
"""
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(20):
    t = Thread(target=task,args=(i,))
    t.start()
View Code

 

四.event事件

event:用于线程与线程之间的状态的判定

方法

GIL全局解释锁.死锁与递归锁
event.isSet():返回event的状态值;

event.wait():event的状态为False将进入阻塞态,为True时解除阻塞态

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。
View Code

示例

GIL全局解释锁.死锁与递归锁
event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。
View Code

 

五.队列在线程中的使用

线程间可以进行直接通信,但使用队列的话,可以避免手动加锁的问题,减少死锁问题的发生

Queue  # FIFO

GIL全局解释锁.死锁与递归锁
import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
View Code

 

LifoQueue  # Last in First out

GIL全局解释锁.死锁与递归锁
import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
View Code

 

PriorityQueue #存储数据时可设置优先级的队列,值越小优先级越高

GIL全局解释锁.死锁与递归锁
import queue

q=queue.PriorityQueue()
q.put(10,'first')
q.put(-100,'second')
q.put(20,'third')

print(q.get())
print(q.get())
print(q.get())
View Code

 

上一篇:你是否真的了解全局解析锁(GIL)


下一篇:GIL全局解释器锁、递归锁、