GIL全局解释器锁/Event事件/信号量/线程Queue

GLL全局解释器锁

背景:python解释器有很多种,最常见的就是Cpython解释器

定义:GIL本质也是一把互斥锁:将并发变成串行,牺牲效率保证数据的安全;用来阻止同一个进程下的多个线程同时执行(同一个进程内多个线程无法实现并行,但是可以实现并发)

注意:python的多线程没法利用多核优势,并不是没用了

GIL的作用与意义:

  • GIL的存在是因为Cpython解释器的内存管理是不安全的
  • GIL是Cpython解释器的特点,且单进程下多个线程无法利用多核优势是所有解释器语言的通病
  • 针对不同数据应该加不同的锁进行处理
  • GIL用来保证线程安全

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

  • 若有四个任务,且是计算密集型
    • 单核情况下
      • 开线程更省资源
    • 多核情况下
      • 开进程时间少点
      • 开线程时间长点
# 计算密集型
from multiprocessing import Process
from threading import Thread
import os,time
​
def work():
     res=0
     for i in range(100000000):
        res*=i
​
if __name__ == '__main__':
     l=[]
     print(os.cpu_count())  # 本机为6核
     start=time.time()
     for i in range(6):
          p=Process(target=work) # 耗时  4.732933044433594
         p=Thread(target=work) # 耗时 22.83087730407715
         l.append(p)
         p.start()
     for p in l:
         p.join()
     stop=time.time()
     print('run time is %s' %(stop-start))

若有四个任务,且是IO密集型

  • 单核情况下
    • 开线程更节省资源
  • 多核情况下
    • 开线程更节省资源
# 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()) #本机为6核
    start=time.time()
    for i in range(4000):
        p=Process(target=work) # 耗时9.001083612442017s多,大部分时间耗费在创建进程上
        # p=Thread(target=work) # 耗时2.051966667175293s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

总结:

  • python的多线程到底有没有用,需要看情况而定,首先可以肯定是有用的
  • 多进程+多线程配合使用

GIL与普通的互斥锁

from threading import Thread
import time
​
n = 100def 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)  # 99

死锁

PS:只要类加括号实例化对象,无论传入的参数是否一样,生成的对象肯定不一样,单例模式除外

注意:自己千万不要轻易的处理锁的问题

from threading import Thread,Lock
import time
​
mutexA = Lock()
mutexB = Lock()
​
class MyThread(Thread):
    def run(self):  # 创建线程自动触发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()
'''
Thread-1抢到了A锁
Thread-1抢到了B锁
Thread-1释放了B锁
Thread-1释放了A锁
Thread-1抢到了B锁
Thread-2抢到了A锁
'''

RLock

定义:RLock是递归锁

  • 可以被第一个抢到锁的人连续的acquire和release
  • 每acquire一次锁身上的计数加1
  • 每release一次锁身上的计数加1
  • 只要锁的计数不为0,其他人都不能抢
from threading import Thread,RLock
import time
​
mutexA = mutexB = RLock()
​
class MyThread(Thread):
    def run(self):  # 创建线程自动触发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()
'''
Thread-1抢到了A锁
Thread-1抢到了B锁
Thread-1释放了B锁
Thread-1释放了A锁
Thread-1抢到了B锁
Thread-1抢到了A锁
Thread-1释放了A锁
Thread-1释放了B锁
Thread-2抢到了A锁
Thread-2抢到了B锁
Thread-2释放了B锁
Thread-2释放了A锁
...
'''

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(6):
    t = Thread(target=car,args=('伞兵%s'%i,))
    t.start()
'''
红灯正亮着
伞兵0正在等红灯
伞兵1正在等红灯
伞兵2正在等红灯
伞兵3正在等红灯
伞兵4正在等红灯
伞兵5正在等红灯
(三秒后)

绿灯亮了
伞兵4加油门飙车了
伞兵5加油门飙车了
伞兵2加油门飙车了
伞兵3加油门飙车了
伞兵0加油门飙车了
伞兵1加油门飙车了
'''

信号量

  • 互斥锁类似一个厕所(一个坑位)
  • 信号量类似公共厕所(多个坑位)
from threading import Semaphore,Thread
import time
import random
​
sm = Semaphore(5)  # 造了一个有5个坑位的公共厕所
def task(name):
    sm.acquire()
    print('%s占了一个坑位'%name)
    time.sleep(random.randint(1,3))
    sm.release()
    
for i in range(10):
    t = Thread(target=task,args=(i,))
    t.start()
'''
0占了一个坑位
1占了一个坑位
2占了一个坑位
3占了一个坑位
4占了一个坑位
5占了一个坑位
7占了一个坑位
6占了一个坑位
8占了一个坑位
9占了一个坑位
'''

线程Queue

同一个进程下的多个线程本来就是数据共享,为什么还要用队列?

  • 因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题
  • 因为锁操作不当极容易产生死锁现象
import queue
​
q = queue.Queue()
q.put('hahha')
print(q.get())
​
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())
​
q = queue.PriorityQueue()
# 数字越小 优先级越高
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get())
'''
hahha
3
(-10, 'yyyy')
'''
原文地址:https://www.cnblogs.com/zhukaijian/p/11352370.html