线程(二)---GIL、同步锁、死锁与递归锁

线程需要掌握的知识点:

  1. 同步锁
  2. 死锁 递归锁
  3. 信号量和同步对象(了解)
  4. 队列---生产者消费者模式
  5. 进程

线程相关概念

  •     并发 &并行

并发:是指系统具有处理多个任务(动作)的能力

并行:是指系统具有同时处理多个任务(动作)的能力

 根据定义,可以得出并行是并发的子集。

线程(二)---GIL、同步锁、死锁与递归锁_第1张图片

  • 同步与异步

同步:当进程执行到一个IO(等待外部数据)的时候 ------等:同步

异步                                                                               -------不等:异步

 

  • GIL:全局解释锁

无论你启动多少个线程,你有多少个cpu,Python在执行的时候在同同一时刻只允许一个线程运行

线程(二)---GIL、同步锁、死锁与递归锁_第2张图片

假设有三个线程add,mul和main,因为GIL造成他们执行时是必须相互竞争来回切换运行线程,无法并发,当涉及到IO操作就会切换,你是不是有个疑惑,那当初为什么要搞这个GIL,这是因为是要在解释器层面保证线程安全,JAVA虽然没有GIL,但是在用户层面需要加锁来保证线程安全,这也需要消耗大量资源。

那该怎么办呢?解锁GIL吗,在实践证明,解锁后效率更低,因为前人开发的模块都是在GIL基础上开发的,当去掉锁后效率自然会降低。这里给大家介绍一种好办法:

多进程+协程 

后面会具体讲解该方法

线程任务可分为:

任务:IO密集型

          计算密集型

对于IO密集型的任务:Python的多线程有意义,可以采用多进程+协程。

对计算密集型的任务:python多线程不推荐,python就不适用了。

 

同步锁:

先看下面例子



def add():
    global num
    temp = num
    time.sleep(0.1)
    num = temp+1
num = 0
import  threading
import  time
l= []
for i in range(100):
    t1 = threading.Thread(target=add)
    t1.start()
    l.append(t1)


for t in l:
    t.join()

print(num)

照正常情况下num最后应该是100,但是结果却不是100,这是为什么呢,因为当线程执行到sleep处,睡眠,由于sleep涉及到IO操作,所以此时线程会切换,切换到的线程运行到sleep处也睡眠切换,结果到时很多个线程堆积到temp=num那里,所以导致temp大多都是0,可想而知,最终结果也就不为100了。那有什么解决办法吗?当然有的,咋们可以添加线程锁把可能造成堆积的地方给锁住,让cpu在把锁住内容执行完毕后在切换。这里采用threading.Lock().acquire()加锁和threading.Lock().release()解锁

如下



def add():
    global num
    lock.acquire() #将下面的内容锁住,只能有一个线程执行
    temp = num

    time.sleep(0.1)
    num = temp+1
    lock.release() #解锁
num = 0
import  threading
import  time
l= []
lock = threading.Lock()
for i in range(100):
   
    t1 = threading.Thread(target=add)
    t1.start()
    l.append(t1)


for t in l:
    t.join()

print(num)

虽然解决了堆积问题,但是加锁后就相当于串行了,效率也随之下降。在开发过程中要尽量减少加锁的内容和加锁范围,避免造成线程死锁。

在线程件共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在外力里作用下将一直等待下去。下面就是一个死锁的例子

import threading, time
#死锁,第一个线程执行完actionA后执行actionB,第一个线程拿到B锁,而第二个线程在actionA中想要获得#B锁,
#但是他被第一个线程用了,此时死锁
#输出下面
#Thread-1gotA--- Wed Feb 20 15:31:37 2019
#Thread-1 gotB--- Wed Feb 20 15:31:39 2019
#Thread-2gotA--- Wed Feb 20 15:31:40 2019
#Thread-1 gotB--- Wed Feb 20 15:31:41 2019------在这里卡住了
class Mythread(threading.Thread):
    def actionA(self):
        A.acquire()
        print(self.name + "gotA---",time.ctime())
        time.sleep(2)
        B.acquire()
        print(self.name,"gotB---",time.ctime())
        time.sleep(1)
        B.release()
        A.release()


    def actionB(self):
        B.acquire()
        print(self.name,"gotB---",time.ctime())
        time.sleep(2)
        A.acquire()
        print(self.name,"gotA----",time.ctime())
        time.sleep(1)
        A.release()
        B.release()


    def run(self):
        self.actionA()
        time.sleep(0.5)
        self.actionB()
        pass


if __name__ == "__main__":
    A = threading.Lock()
    B = threading.Lock()
    l = []
    for i in range(5):
        t = Mythread()
        t.start()
        l.append(t)
    for i in l:
        i.join()
    print("end --------")

 

递归锁

为了支持在同一线程中多次请求同一资源,python提供了"可重入锁":threading.Rlock。RLock内部维护者一个Lock和一个counter变量,counter记录了acquire的次数,从而式的资源可以呗多谢acquire。知道一个线程所有acquire都被release,其它的线程才能获得资源。

使用递归锁RLock解决死锁,加锁时计数器加一,当计数器大于0,只有本线程可用该锁,其它线程就用不了,当解锁时计数器减一,只有计数器为0是其它线程才能竞争执行,从而避免了死锁。

import threading, time

class Mythread(threading.Thread):
    def actionA(self):
        r_lock.acquire() #计数器加一
        print(self.name + "gotA---",time.ctime())
        time.sleep(2)
        r_lock.acquire()#计数器加一
        print(self.name,"gotB---",time.ctime())
        time.sleep(1)
        r_lock.release()#计数器减一
        r_lock.release()#计数器减一


    def actionB(self):
        r_lock.acquire()#计数器加一
        print(self.name,"gotB---",time.ctime())
        time.sleep(2)
        r_lock.acquire()#计数器加一
        print(self.name,"gotA----",time.ctime())
        time.sleep(1)
        r_lock.release()#计数器减一

        r_lock.release()#计数器减一

    def run(self):
        self.actionA()
        time.sleep(0.5)
        self.actionB()



if __name__ == "__main__":
    # 使用递归锁RLock解决死锁, 加锁时计数器加一,当计数器大于0,只有本线程可用该锁,其它线程就用
#不了,
    # 当解锁时计数器减一,只有计数器为0是其它线程才能竞争执行,从而避免了死锁。
    r_lock=threading.RLock() #递归锁
    l = []
    for i in range(5):
        t = Mythread()
        t.start()
        l.append(t)
    for i in l:
        i.join()
    print("end --------")

这次就正常执行了!

 

线程(二)---GIL、同步锁、死锁与递归锁_第3张图片

 

 

 

你可能感兴趣的:(Python基础,Python)