GIL全局解释器锁

一、GIL介绍

GIL是一个互斥锁:将并发编程串行,降低了效率但保证了数据的安全

Cpython解释器中才有的概念,不是python的特点

保护不同的数据安全,就应该加不同的锁

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

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

二、GIL解释

每次运行python解释器都会产生进程,进程下会有多个线程

如图所示:因为python解释器有垃圾回收机制,所以每次运行python解释器都会自带一个垃圾回收线程,假设该进程下还有3个线程,如果四个线程需要运行,需要去解释器那抢锁(GIL互斥锁),谁抢到了,谁就运行。这样就能防止,如果四个线程运行,线程1申请变量a =1 ,刚申请了一个变量,垃圾回收机制就把它回收。

 

 

三、GIL与多线程

研究python的多线程是否需有用需分情况讨论

四个任务:计算密集的任务,每个任务耗时10秒

单核情况下:

多线程好一点,消耗资源少一点

多核情况下:

开四个进程:10s多一点

开四个线程:40s多一点

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())  # 本机为4核
    start=time.time()
    for i in range(6):
        p=Process(target=work) # 多进程耗时 3.0867862701416016
        # p=Thread(target=work) # 多线程耗时  5.1183037757873535
        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()) #本机为4核
    start=time.time()
    for i in range(100):
        # p=Process(target=work) # 多进程耗时13.326009750366211多,大部分时间耗费在创建进程上
        p=Thread(target=work) # 多线程耗时2.0372188091278076多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))
IO密集型:多线程更好

四、GIL与普通的锁

from threading import Thread
import time

n = 100


def task():
    global n
    tmp = n
    time.sleep(1)  # 如果在此睡1秒,就会自动释放锁,其他99个人都会抢到锁,打印结果:99;如果不睡,那么此锁就会一直在第一个人手中,打印结果:0
    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)

五、死锁现象与递归锁

1.死锁现象

一共有A与B两把锁,两个人在抢锁的过程中会出现抢到对方的锁,却被锁在自己的房间,因而产生了死锁现象。

from threading import Thread,Lock,current_thread,RLock
import time
"""
Rlock可以被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0 其他人都不能抢

"""
 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()
    t.start()
死锁现象

2.递归锁

A与B变成了一把锁,这样只有在一个人抢到锁时,别人就无法抢锁,只有等这个人释放了锁,别人才可以抢锁。

from threading import Thread,Lock,current_thread,RLock
import time
"""
Rlock可以被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0 其他人都不能抢

"""

mutexA = mutexB = RLock()  # A B现在是同一把锁


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()
递归锁

六、信号量

"""
互斥锁:一个厕所(一个坑位)
信号量:公共厕所(多个坑位)
"""
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()
抢厕所例子

七、Even

同进程的一样

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的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()
红绿灯事件

八、线程queue

1.class queue.Queue()

import queue
q = queue.Queue()
q.put('1')
q.put('2')
q.put('3')
print(q.get())
print(q.get())
print(q.get())


打印结果:1  2  3
先进先出

2.class queue.LifoQueue()

import queue
q = queue.LifoQueue()
q.put('1')
q.put('2')
q.put('3')
print(q.get())
print(q.get())
print(q.get())


打印结果:3  2  1
先进后出

3.class queue.priorityQueue()

import queue
q = queue.PriorityQueue()
q.put(5)
q.put(-2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())


打印结果:-2  3  5
按优先级顺序:数字越小,优先级越高
原文地址:https://www.cnblogs.com/xiongying4/p/11352856.html