zoukankan      html  css  js  c++  java
  • 守护线程和锁

    import threading
    # 定义后台线程的线程执行体与普通线程没有任何区别
    def action(max):
        for i in range(max):
            print(threading.current_thread().name + "  " + str(i))
    t = threading.Thread(target=action, args=(100,), name='后台线程')
    # 将此线程设置成后台线程
    # 也可在创建Thread对象时通过daemon参数将其设为后台线程
    t.daemon = True
    # 启动后台线程
    t.start()
    for i in range(10):
        print(threading.current_thread().name + "  " + str(i))
    # -----程序执行到此处,前台线程(主线程)结束------
    # 后台线程也应该随之结束

    有一种线程,它是在后台运行的,它的任务是为其他线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。Python 解释器的垃圾回收线程就是典型的后台线程。
    后台线程有一个特征,如果所有的前台线程都死亡了,那么后台线程会自动死亡。

    调用 Thread 对象的 daemon 属性可以将指定线程设置成后台线程。下面程序将指定线程设置成后台线程,可以看到当所有的前台线程都死亡后,后台线程随之死亡。当在整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以程序也就退出了。

    当前台线程死亡后,Python 解释器会通知后台线程死亡,但是从它接收指令到做出响应需要一定的时间。如果要将某个线程设置为后台线程,则必须在该线程启动之前进行设置。也就是说,将 daemon 属性设为 True,必须在 start() 方法调用之前进行,否则会引发 RuntimeError 异常。

    互斥锁同步线程

    为了解决这个问题,Python 的 threading 模块引入了互斥锁(Lock)。threading 模块提供了 Lock 和 RLock 两个类,它们都提供了如下两个方法来加互斥锁和释放互斥锁:

    1. acquire(blocking=True, timeout=-1):请求对 Lock 或 RLock 加锁,其中 timeout 参数指定加锁多少秒。
    2. release():释放锁。


    Lock 和 RLock 的区别如下:

    • threading.Lock:它是一个基本的锁对象,每次只能锁定一次,其余的锁请求,需等待锁释放后才能获取。
    • threading.RLock:它代表可重入锁(Reentrant Lock)。对于可重入锁,在同一个线程中可以对它进行多次锁定,也可以多次释放。如果使用 RLock,那么 acquire() 和 release() 方法必须成对出现。如果调用了 n 次 acquire() 加锁,则必须调用 n 次 release() 才能释放锁。


    由此可见,RLock 锁具有可重入性。也就是说,同一个线程可以对已被加锁的 RLock 锁再次加锁,RLock 对象会维持一个计数器来追踪 acquire() 方法的嵌套调用,线程在每次调用 acquire() 加锁后,都必须显式调用 release() 方法来释放锁。所以,一段被锁保护的方法可以调用另一个被相同锁保护的方法。

    Lock 是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程在开始访问共享资源之前应先请求获得 Lock 对象。当对共享资源访问完成后,程序释放对 Lock 对象的锁定。

    在实现线程安全的控制中,比较常用的是 RLock。通常使用 RLock 的代码格式如下:

    class X:
        #定义需要保证线程安全的方法
        def m () :
            #加锁
            self.lock.acquire()
            try :
                #需要保证线程安全的代码
                #...方法体
            #使用finally 块来保证释放锁
            finally :
                #修改完成,释放锁
                self.lock.release()

    使用 RLock 对象来控制线程安全,当加锁和释放锁出现在不同的作用范围内时,通常建议使用 finally 块来确保在必要时释放锁。
    通过使用 Lock 对象可以非常方便地实现线程安全的类,线程安全的类具有如下特征:

      • 该类的对象可以被多个线程安全地访问。
      • 每个线程在调用该对象的任意方法之后,都将得到正确的结果。
      • 每个线程在调用该对象的任意方法之后,该对象都依然保持合理的状态。
      • import threading
        import time
        
        
        class Account():
            def __init__(self,balance,account_no):
                # 封装账户编号、账户余额的两个成员变量
                self._balance=balance#余额
                self.account_no=account_no#账户
                self.lock=threading.RLock() #创建Rlock锁
        
            def drawMoney(self,money):
                #对线程加锁
                self.lock.acquire()
                try:
                    if self._balance>=money:
                        print(threading.current_thread().name+"取钱成功!吐出钞票:"+str(money))
                        self._balance-=money
                        time.sleep(0.01)
                        print("余额为:"+str(self._balance))
                    else:
                        print(threading.current_thread().name
                            + "取钱失败!余额不足!")
                finally:
                    #释放线程锁
                    self.lock.release()
        
        def draw(account,money):
            account.drawMoney(money)
        
        # 创建一个账户
        acct =Account(2000,"we2234")
        
        threading.Thread(target=draw,name="",args=(acct,800)).start()
        threading.Thread(target=draw,name="",args=(acct,700)).start()
        threading.Thread(target=draw,name="",args=(acct,600)).start()

        程序中 RLock 对象作为同步锁,线程每次开始执行 draw() 方法修改 self.balance 时,都必须先对 RLock 对象加锁。当该线程完成对 self._balance 的修改,将要退出 draw() 方法时,则释放对 RLock 对象的锁定。这样的做法完全符合“加锁→修改→释放锁”的安全访问逻辑。

        当一个线程在 draw() 方法中对 RLock 对象加锁之后,其他线程由于无法获取对 RLock 对象的锁定,因此它们同时执行 draw() 方法对 self._balance 进行修改。这意味着,并发线程在任意时刻只有一个线程可以进入修改共享资源的代码区(也被称为临界区),所以在同一时刻最多只有一个线程处于临界区内,从而保证了线程安全。

        为了保证 Lock 对象能真正“锁定”它所管理的 Account 对象,程序会被编写成每个 Account 对象有一个对应的 Lock(就像一个房间有一个锁一样)。

        上面的 Account 类增加了一个代表取钱的 draw() 方法,并使用 Lock 对象保证该 draw() 方法的线程安全,而且取消了 setBalance() 方法(避免程序直接修改 self._balance 成员变量),因此线程执行体只需调用 Account 对象的 draw() 方法即可执行取钱操作。

  • 相关阅读:
    《Linux内核分析》第七周学习笔记
    《深入理解计算机系统》第七章学习笔记
    《Linux内核设计与实现》第三章学习笔记
    《Linux内核分析》第六周学习笔记
    《Linux内核设计与实现》第十八章学习笔记
    《Linux内核分析》第五周学习笔记
    20182319彭淼迪 2018-2019-1《程序设计与数据结构》课程总结
    实验九报告
    第十周学习总结
    haffman树的实现
  • 原文地址:https://www.cnblogs.com/jzxs/p/11420023.html
Copyright © 2011-2022 走看看