• Python学习 :多线程 --- 锁


     多线程

      什么是锁?

      - 锁通常被用来实现对共享资源的同步访问

      - 为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:

      GIL(Global Interpreter Lock) 全局的解释器锁

      增加锁的目的:

      1.虽然效率十分低,但保证了数据的安全性

      2.不同的锁对应保护不同的数据

      3.谁拿到GIL锁就让谁得到Cpython解释器的执行权限

      4.GIL锁保护的是Cpython解释器数据的安全,而不会保护你自己程序的数据的安全

      5.GIL锁当遇到阻塞的时候,就被迫把锁给释放了,那么其他的就开始抢锁了,抢到后把值进行修改,但是第一个拿到锁的还依旧保持着原本的数据,当再次拿到锁的时候,数据已经修改了,而第一位拿的还是原来的数值,这样就造成了混乱,也就保证不了数据的安全了。

      同步锁Lock

      - GIL与Lock是两把锁,保护的数据不一样,前者是解释器级别的(保护的是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据

      实例:没有加上锁的情况

      - 在执行这个操作的多条bytecodes期间的时候可能中途就换别的线程了,这样就出现了data races的情况

    import time
    import threading
    def addNum():
        global num # 在每个线程中都获取这个全局变量
        temp = num
        print('--get num:',num )
        time.sleep(0.01)
        num = temp - 1 # 对此公共变量进行-1操作
    
    start = time.time()
    num = 100  # 设定一个共享变量
    thread_list = []
    for i in range(100):
        t = threading.Thread(target=addNum)
        t.start()
        thread_list.append(t)
    
    for t in thread_list: # 等待所有线程执行完毕
        t.join()
    
    print('final num:', num )
    end = time.time()
    print(end - start)
    # 此时并不能获取到正确的答案0
    # 100个线程每一个一定都没有执行完就进行了切换,我们说过sleep就等效于IO阻塞,1s之内不会再切换回来,所以最后的结果一定是99.
    # 多个线程都在同时操作同一个共享资源,所以造成了资源破坏
    

      实例:加上锁的情况

      - 同步锁保证了在同一时刻只有一个线程被执行

    import time
    import threading
    
    def addNum():
        global num # 在每个线程中都获取这个全局变量
        lock.acquire() # 获取锁
        temp = num
        print('--get num:',num )
        num = temp - 1 # 对此公共变量进行-1操作
        lock.release() # 只有在执行完上述内容才会释放锁
    
    start = time.time()
    num = 100  # 设定一个共享变量
    thread_list = []
    lock = threading.Lock()
    
    for i in range(100):
        t = threading.Thread(target=addNum)
        t.start()
        thread_list.append(t)
    
    for t in thread_list: #等待所有线程执行完毕
        t.join()
    
    print('final num:', num )
    end = time.time()
    print(end - start)
    

      死锁Lock

      线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁的情况

      因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去

    import threading,time
    
    class myThread(threading.Thread):
        def LoA(self):
            lockA.acquire()
            print(self.name,"got lockA",time.ctime())
            time.sleep(3)
            lockB.acquire()
            print(self.name,"got lockB",time.ctime())
            lockB.release()
            lockA.release()
        def LoB(self):
            lockB.acquire()
            print(self.name,"got lockB",time.ctime())
            time.sleep(2)
            lockA.acquire()
            print(self.name,"got lockA",time.ctime())
            lockA.release()
            lockB.release()
        def run(self):
            self.LoA()
            self.LoB()
    
    if __name__=="__main__":
        lockA=threading.Lock()
        lockB=threading.Lock()
        # 解决方案:添加 lock = threading.RLock()
        # lock = threading.RLock()
        threads=[]
        for i in range(3):
            threads.append(myThread())
        for t in threads:
            t.start()
        for t in threads:
            t.join()
        # 此时线程会卡死,一直等待下去,此时添加递归锁即可解决死锁的问题
    

      递归锁 RLock

      在添加递归锁后,RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源

      信号量

      信号量用来控制线程并发数的,它也是一把锁,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1

      计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)

      BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常

    import threading,time
    class myThread(threading.Thread):
        def run(self):
            if semaphore.acquire(): # 如果上信号量锁就往下进行
                print(self.name)
                time.sleep(5)
                semaphore.release()
    if __name__=="__main__":
        semaphore = threading.Semaphore(5) # 一次允许5个线程进行
        # semaphore = threading.BoundedSemaphore(5) # 与上述效果一致
        thrs = []
        for i in range(20): # 开启20个线程
            thrs.append(myThread())
        for t in thrs:
            t.start()
    

      条件变量同步

      有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了wait()、notify()、notifyAll()方法

      lock_con = threading.Condition([Lock / Rlock]): 默认创建一个RLock锁,用于线程间的通信

      wait():条件不满足时调用,线程会释放锁并进入等待阻塞

      notify():条件创造后调用,通知等待池激活一个线程

      notifyAll():条件创造后调用,通知等待池激活所有线程

    import threading,time
    from random import randint
    
    class Producer(threading.Thread):
        def run(self):
            global L
            while True:
                val=randint(0,100)
                print('生产者',self.name,":Append"+str(val),L)
                if lock_con.acquire():
                    L.append(val)
                    lock_con.notify() # 激活一个线程
                    lock_con.release()
                time.sleep(3)
    class Consumer(threading.Thread):
        def run(self):
            global L
            while True:
                    lock_con.acquire() # wait()过后,从此处开始进行
                    if len(L)==0:
                        lock_con.wait() # 进入等待阻塞
                        print('继续进行') # 我们可以看到并没有打印这个话,这说明线程不是从wait()下面继续进行
                    print('消费者',self.name,":Delete"+str(L[0]),L)
                    del L[0]
                    lock_con.release()
                    time.sleep(0.25)
    
    if __name__=="__main__":
    
        L = []
        lock_con = threading.Condition()
        threads = []
        for i in range(5):
            threads.append(Producer())
        threads.append(Consumer())
        for t in threads:
            t.start()
        for t in threads:
            t.join()
    

      同步条件(Event)

      条件同步和条件变量同步差不多意思,只是少了锁功能,同步条件不是锁

      因为条件同步设计于不访问共享资源的条件环境。event = threading.Event():条件环境对象,初始值为False;

      event.isSet():返回event的状态值

      event.wait():如果 event.isSet()==False将阻塞线程

      event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度

      event.clear():恢复event的状态值为False

    import threading,time
    import random
    def light():
       if not event.isSet():
            event.set() #wait就不阻塞 #绿灯状态
        count = 0
    while True: if count < 10: print('33[42;1m--green light on---33[0m') elif count <13: print('33[43;1m--yellow light on---33[0m') elif count <20: if event.isSet(): event.clear() print('33[41;1m--red light on---33[0m') else: count = 0 event.set() # 打开绿灯 time.sleep(1) count += 1 def car(n): while 1: time.sleep(random.randrange(10)) if event.isSet(): # 绿灯 print("car [%s] is running.." % n) else: print("car [%s] is waiting for the red light.." %n) if __name__ == '__main__': event = threading.Event() Light = threading.Thread(target=light) Light.start() for i in range(3): t = threading.Thread(target=car,args=(i,)) t.start()
  • 相关阅读:
    作业: 小型购物系统1---按顺序编写
    字典操作学习小结
    字符操作学习笔记小结
    列表,元组等学习笔记小结
    模块及其数据类型小结
    python3学习笔记----基础知识1
    压力山大
    下周一开始上班啦!
    凌晨12点,沉迷学习,无法自拔...
    web前端开发2018年12月找工作总结
  • 原文地址:https://www.cnblogs.com/ArticleYeung/p/10668568.html
Copyright © 2020-2023  润新知