GIL锁、死锁、递归锁、信号量、Evnet事件、线程Queue

今日内容

1、全局解释器锁GIl
2、GIL与自定义互斥锁
3、死锁与递归锁(了解)
4、信号量(了解)
5、Event事件
6、线程Queue

一、全局解释器锁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.)
'''
结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

关于垃圾回收机制,在python解释器中,垃圾回收机制与其他线程不是串行的,但也不是并行的。
如果是并行的话,就会出现错误回收数据的情况:
	x=10的操作在执行到给10开空间的时候,还没引用x,垃圾回收机制就会把10给回收掉。
为了解决这一问题,就给CPython解释器上了一个互斥锁

# 原理是:
1、首先线程想运行就要调用CPython解释器
2、为了不让垃圾回收机制与其他线程并行,就给CPython上了一个互斥锁,使得同一时间只有一个线程能使用CPython解释器
3、抢到GIL全局互斥锁的线程可以运行,一旦遇到IO操作便会返回GIL锁外层重新解释器执行代码

结论:GIl就是一把互斥锁,CPython解释器的互斥锁(牺牲了效率,保证了数据的安全)

# python中多线程到底有没有用?
单核情况下:四个任务
多核情况下:四个任务

计算密集型: 一个任务算十秒,四个进程和四个线程,肯定是进程快
IO密集型: 任务都是纯IO情况下,线程开销比进程小,肯定是线程好

测试代码:
# 计算密集型
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(12):
        # p=Process(target=work) #耗时7.3804872035980225
        p=Thread(target=work) #耗时10.810997724533081
        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 os,time


def work():
    time.sleep(2)


if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        # p=Process(target=work)  #耗时4.527513027191162,大部分时间耗费在创建进程上
        p=Thread(target=work)  #耗时2.0667171478271484多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

二、GIL与自定义互斥锁

不同的数据需要加不同的锁才能实现数据安全,GIL只是对线程加锁,对数据没有加锁效果

代码示例:
from threading import Thread,Lock
import time

mutex=Lock()  # 在要修改的数据上方创建锁
n=100
def task():
    global n
    with mutex:  # 在获得数据之前抢锁  with mutex的代码块结束后自动释放锁,同with open
        temp=n
        time.sleep(0.1)
        n=temp-1

if __name__ == '__main__':
    l=[]
    for i in range(100):
        t=Thread(target=task)
        l.append(t)
        t.start()

    for t in l:
        t.join()
    print(n)
# 对于修改不同的数据,需要加不同的锁进行处理

三、死锁与递归锁(了解)

自定义锁一次acquire必须对应一次release,不能连续acquire

递归锁可以连续的acquire,每acquire一次计数加一

递归锁:Rlock

递归锁解决死锁 代码示例:
from threading import Thread,Lock,RLock
import time

# mutexA=Lock()
# mutexB=Lock()
mutexB=mutexA=RLock()


class Mythead(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()  # acquire的计数+1  一旦acquire的计数不为0,其他进程就抢不到锁,就堵塞
        print('%s 抢到A锁' %self.name)
        mutexB.acquire()  # acquire的计数+1
        print('%s 抢到B锁' %self.name)
        mutexB.release()  # acquire的计数-1
        mutexA.release()  # acquire的计数-1

    def f2(self):
        mutexB.acquire()
        print('%s 抢到了B锁' %self.name)
        time.sleep(2)
        mutexA.acquire()
        print('%s 抢到了A锁' %self.name)
        mutexA.release()
        mutexB.release()

if __name__ == '__main__':
    for i in range(100):
        t=Mythead()
        t.start()

四、信号量(了解)

自定义的互斥锁如果是一个厕所,那么信号量就相当于公共厕所,门口挂着多个厕所的钥匙。抢和释放跟互斥锁一致

代码示例:
from threading import Thread, Semaphore
import time
import random
# sm = Lock() 换下面一行的创建锁方法
sm = Semaphore(5)  # 公共厕所里面有五个坑位,在厕所外面放了五把钥匙
# 可以理解为一个放数据的容器,取出数据用完之后放回容器中,没有数据了就堵塞

def task(name):
    sm.acquire()
    print('%s正在蹲坑' % name)
    # 模拟蹲坑耗时
    time.sleep(random.randint(1, 5))
    sm.release()


if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=task, args=('伞兵%s号' % i,))
        t.start()

五、Event事件

一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样

红路灯 代码示例:
from threading import Thread, Event
import time

event = Event()  # 创建一个红绿灯


def light():
    print('is red light...')
    time.sleep(3)
    event.set()  # 发出信号
    print('is green light...')


def car(name):
    print('car%s is wait red light...' % name)
    event.wait()  # 接收到信号才会通行,否则堵塞
    print('car%s is running...' % name)


if __name__ == '__main__':
    l = Thread(target=light)
    l.start()
    for i in range(10):
        c = Thread(target=car, args=('车辆%s' % i, ))
        c.start()

六、线程Queue

# 我们现在的q只能在本地使用,后面我们会学基于网络的q 
import queue

queue.Queue() #先进先出
q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())  # 1
print(q.get())  # 2
print(q.get())  # 3

queue.LifoQueue() #后进先出->堆栈
q=queue.LifoQueue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())  # 3
print(q.get())  # 2
print(q.get())  # 1

queue.PriorityQueue() #优先级
q=queue.PriorityQueue(3) #优先级,优先级用数字表示,数字越小优先级越高
q.put((10,'a'))
q.put((-1,'b'))
q.put((100,'c'))
print(q.get())  # (-1, 'b')
print(q.get())  # (10, 'a')
print(q.get())  # (100, 'c')
posted @ 2019-05-08 16:19  输诚  阅读(160)  评论(0编辑  收藏  举报