GIL、Event事件、信号量、死锁、递归锁

去幕布 >>

GIL全局解释器锁

Python解释器有很多种,最常见的就是CPython解释器
GIL本质也是一把互斥锁:将并发变成串行,牺牲效率保证数据的安全​
用来阻止同一个进程下的 多个线程的 同时执行
(同一个进程内的多个线程无法实现并行,但是可以实现并发)

Python的多线程没法利用多核优势,是不是就没有用了?

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

垃圾回收机制:
a. 引用计数
b. 标记清除
c. 分代回收

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

(假设有四个任务,每个任务需要10s 处理完成)​

1. 计算密集型​:
单核情况下​:
​开线程更省资源(原因是开进程需要申请内存空间等操作,浪费时间)
多核情况下:
开进程处理    10s
开线程处理    40s


2. I/O密集型:
​​​​​单核情况下:
开线程更省资源(原因和计算密集型的一样)
多核情况下:
​​​开线程更省资源
​(原因是 I/O秘籍情况下,线程工作方式为 切换+保存
​进程申请内存空间,是要消耗很长时间的,
而​线程的切换+保存基本不消耗时间)
​

python的多线程到底有没有用
需要看情况而定  并且肯定是有用的
多进程+多线程配合使用​
View Code
# I/O密集型
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())  # 本机为8核
    start=time.time()
​
    for i in range(400):
        p=Process(target=work)  # 耗时9.506524085998535s多,大部分时间耗费在创建进程上
        # p=Thread(target=work)  # 耗时 2.0459132194519043 s多
        l.append(p)
        p.start()
​
    for p in l:
        p.join()
​
    stop=time.time()
    print('run time is %s' %(stop-start))
I/O密集型
# 计算密集型
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())  # 本机为8核
    start=time.time()
​
    for i in range(8):
        p=Process(target=work)  # 耗时  8.460614204406738s
        # p=Thread(target=work)  # 耗时 34.822699308395386s
        l.append(p)
        p.start()
​
    for p in l:
        p.join()
​
    stop=time.time()
    print('run time is %s' %(stop-start))
计算密集型

死锁

1. 代码表示下一步要acquire一个锁
2. ​但是两个线程互相拿着对方需要的锁,于是程序就hang到这儿了
这种情况就是死锁
from threading import Thread, Lock, current_thread
import time
​
mutexA = Lock()
mutexB = Lock()​​
​
class MyThread(Thread):
​
    # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
    def run(self):  
        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锁
​(程序到这儿并没有结束,而是一直hang着...)
​
​​​'''
死锁代码示例

 递归锁

Rlock可以被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0 其他人都不能抢
from threading import Thread, Lock, current_thread, RLock
import time
​
# mutexA = Lock()
# mutexB = Lock()​​
mutexA = mutexB = RLock()  # A B现在是同一把锁​
class MyThread(Thread):
​
    # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
    def run(self):  
        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(2):
    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锁
Thread-2抢到了B锁
Thread-2抢到了A锁
Thread-2释放了A锁
Thread-2释放了B锁
​​'''
递归锁代码示例

Event事件

前言
三两赛车准备出发,但是要等信号灯打出绿灯信号时,才出发

Event事件
事件处理的机制。
全局定义了一个内置标志Flag,
Flag默认值为 False,那么当程序执行 event.wait方法时就会阻塞,
如果Flag值为True,那么event.wait 方法时便不再阻塞。

涉及的方法:
set(): 将标志设为True,并通知所有处于等待阻塞状态的线程恢复运行状态。
clear(): 将标志设为False。
wait(timeout): 如果标志为True将立即返回,否则阻塞线程至等待阻塞状态,等待其他线程调用set()。
isSet(): 获取内置标志状态,返回True或False。
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(3):
    t = Thread(target=car,args=('伞兵%s'%i,))
    t.start()
​
'''执行结果:
红灯正亮着
伞兵0正在等红灯
伞兵1正在等红灯
伞兵2正在等红灯
(此处hang3s,因为sleep了3s)​
绿灯亮了
伞兵1加油门飙车了
伞兵0加油门飙车了
伞兵2加油门飙车了
​'''
重现 前言 的情景

 信号量

信号量semaphore
是一个变量,控制着对公共资源或者临界区的访问。
​信号量维护着一个计数器,指定可同时访问资源或者进入临界区的线程数。

实现机制:​​
每次有一个线程获得信号量时,计数器-1。
​若计数器为0,其他线程就停止访问信号量,直到另一个线程释放信号量。
# 信号量代码示例
from threading import Semaphore,Thread
import time
import random
​
sm = Semaphore(5)  # 造了一个含有五个的坑位的公共厕所
def task(name):
    sm.acquire()
    print('%s占了一个坑位'%name)
    time.sleep(random.random())
    sm.release()
    print('%s释放了一个坑位' % name)
​
for i in range(4):
    t = Thread(target=task,args=(i,))
    t.start()
​
'''执行结果:
0占了一个坑位
1占了一个坑位
2占了一个坑位
3占了一个坑位
1释放了一个坑位
0释放了一个坑位
2释放了一个坑位
3释放了一个坑位
​'''​​
信号量代码示例

补充讲解三种队列

同一个进程下的多个线程本来就是数据共享 为什么还要用队列
因为队列是管道+锁  使用队列你就不需要自己手动操作锁的问题 
因为锁操作的不好极容易产生死锁现象
import Queue
​
​q = queue.Queue()
q.put('hahha')
print(q.get())
# 执行结果:hahha
​​
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())
# 执行结果:3​
​​
q = queue.PriorityQueue()
# 数字越小 优先级越高
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get())
# 执行结果:(-10, 'yyyy')​
Queue、LiFoQueue、Priority

 

posted @ 2019-08-14 16:03  不在一城  阅读(232)  评论(0编辑  收藏  举报