• 《Python》线程之锁、信号量、事件、条件、定时器、队列


    一、锁

    线程为什么要有锁:

      += 、-= 赋值操作数据不安全(要经过取值、计算、放回值,3部操作)

      pop 、append 都是数据安全的(只有添加和删除,一次操作)

      队列也是数据安全的

    1、同步锁

    import os, time
    from threading import Thread
    
    def work():
        global n
        temp = n
        time.sleep(0.1)
        n = temp - 1
    
    if __name__ == '__main__':
        n = 100
        l = []
        for i in range(100):
            p = Thread(target=work)
            p.start()
            l.append(p)
        for p in l:
            p.join()
        print(n)    # 结果可能为99
    多个线程抢占资源的情况
    R=threading.Lock()
    R.acquire()
    '''
    对公共数据的操作
    '''
    R.release()
    import os,time
    from threading import Thread,Lock
    
    def work(lock):
        global n
        lock.acquire()
        temp = n
        time.sleep(0.1)
        n = temp - 1
        lock.release()
    
    if __name__ == '__main__':
        lock = Lock()
        n = 100
        l = []
        for i in range(100):
            p = Thread(target=work, args=(lock,))
            p.start()
            l.append(p)
        for p in l:
            p.join()
        print(n)    # 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
    同步锁的作用
    # 不加锁:并发执行,速度快,数据不安全
    
    from threading import current_thread,Thread,Lock
    import os,time
    
    def task():
        global n
        print('%s is running' %current_thread().getName())
        temp=n
        time.sleep(0.5)
        n=temp-1
    
    if __name__ == '__main__':
        n=100
        lock=Lock()
        threads=[]
        start_time=time.time()
        for i in range(100):
            t=Thread(target=task)
            threads.append(t)
            t.start()
        for t in threads:
            t.join()
    
        stop_time=time.time()
        print('主:%s n:%s' %(stop_time-start_time,n))
    
    '''
    Thread-1 is running
    Thread-2 is running
    ......
    Thread-100 is running
    主:0.5216062068939209 n:99
    '''
    
    
    # 不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
    
    from threading import current_thread,Thread,Lock
    import os,time
    
    def task():
        # 未加锁的代码并发运行
        time.sleep(3)
        print('%s start to run' %current_thread().getName())
        global n
        # 加锁的代码串行运行
        lock.acquire()
        temp=n
        time.sleep(0.5)
        n=temp-1
        lock.release()
    
    if __name__ == '__main__':
        n=100
        lock=Lock()
        threads=[]
        start_time=time.time()
        for i in range(100):
            t=Thread(target=task)
            threads.append(t)
            t.start()
        for t in threads:
            t.join()
        stop_time=time.time()
        print('主:%s n:%s' %(stop_time-start_time,n))
    
    '''
    Thread-1 is running
    Thread-2 is running
    ......
    Thread-100 is running
    主:53.294203758239746 n:0
    '''
    
    # 有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
    # 没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
    # start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
    # 单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
    
    from threading import current_thread,Thread,Lock
    import os,time
    
    def task():
        time.sleep(3)
        print('%s start to run' %current_thread().getName())
        global n
        temp=n
        time.sleep(0.5)
        n=temp-1
    
    
    if __name__ == '__main__':
        n=100
        lock=Lock()
        start_time=time.time()
        for i in range(100):
            t=Thread(target=task)
            t.start()
            t.join()
        stop_time=time.time()
        print('主:%s n:%s' %(stop_time-start_time,n))
    
    '''
    Thread-1 start to run
    Thread-2 start to run
    ......
    Thread-100 start to run
    主:350.6937336921692 n:0 #耗时是多么的恐怖
    '''
    互斥锁跟join的区别

    2、死锁与递归锁

      进程也有死锁与递归锁

      所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

    import time
    from threading import Lock
    
    mutexA=Lock()
    mutexA.acquire()
    mutexA.acquire()
    print(123)
    mutexA.release()
    mutexA.release()

      解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

      这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

    import time
    from threading import RLock
    
    mutexA=RLock()
    mutexA.acquire()
    mutexA.acquire()
    print(123)
    mutexA.release()
    mutexA.release()

    典型问题:科学家吃面

    import time
    from threading import Thread,Lock
    
    noodle_lock = Lock()
    fork_lock = Lock()
    def eat1(name):
        noodle_lock.acquire()
        print('%s拿到面条了' % name)
        fork_lock.acquire()
        print('%s拿到叉子了' % name)
        print('%s吃面' % name)
        time.sleep(0.3)
        fork_lock.release()
        print('%s放下叉子' % name)
        noodle_lock.release()
        print('%s放下面' % name)
    
    def eat2(name):
        fork_lock.acquire()
        print('%s拿到叉子了' % name)
        noodle_lock.acquire()
        print('%s拿到面条了' % name)
        print('%s吃面' % name)
        time.sleep(0.3)
        noodle_lock.release()
        print('%s放下面' % name)
        fork_lock.release()
        print('%s放下叉子' % name)
    
    if __name__ == '__main__':
        name_list1 = ['alex', 'wusir']
        name_list2 = ['nezha', 'yuan']
        for name in name_list1:
            Thread(target=eat1, args=(name,)).start()
        for name in name_list2:
            Thread(target=eat2, args=(name,)).start()
    死锁现象
    import time
    from threading import Thread,RLock
    
    fork_lock = noodle_lock = RLock()
    
    def eat1(name):
        noodle_lock.acquire()
        print('%s拿到面条了' % name)
        fork_lock.acquire()
        print('%s拿到叉子了' % name)
        print('%s吃面' % name)
        time.sleep(0.3)
        fork_lock.release()
        print('%s放下叉子' % name)
        noodle_lock.release()
        print('%s放下面' % name)
    
    def eat2(name):
        fork_lock.acquire()
        print('%s拿到叉子了' % name)
        noodle_lock.acquire()
        print('%s拿到面条了' % name)
        print('%s吃面' % name)
        time.sleep(0.3)
        noodle_lock.release()
        print('%s放下面' % name)
        fork_lock.release()
        print('%s放下叉子' % name)
    
    if __name__ == '__main__':
        name_list1 = ['alex', 'wusir']
        name_list2 = ['nezha', 'yuan']
        for name in name_list1:
            Thread(target=eat1, args=(name,)).start()
        for name in name_list2:
            Thread(target=eat2, args=(name,)).start()
    递归锁解决死锁的问题

    二、信号量

    同进程的一样:

      Semaphore管理一个内置的计数器,
      每当调用acquire()时内置计数器-1;
      调用release() 时内置计数器+1;
      计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

    信号量与池的区别:

      是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程

    实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

    import time
    from threading import Semaphore,Thread
    
    def func(index, sem):
        sem.acquire()
        print(index)
        time.sleep(1)
        sem.release()
    
    if __name__ == '__main__':
        sem = Semaphore(5)
        for i in range(10):
            Thread(target=func, args=(i, sem)).start()

    三、事件

    同进程的一样:

      线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

    event.isSet():返回event的状态值;
    event.wait():如果 event.isSet()==False将阻塞线程;
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    event.clear():恢复event的状态值为False。

              

      例如,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作

    import random
    import time
    from threading import Event, Thread
    
    def check(e):
        print('开始检测数据库连接')
        time.sleep(random.randint(1, 5))    # 检测数据库连接
        e.set()  # 成功了
    
    def connect(e):
        for i in range(3):
            e.wait(0.5)
            if e.is_set():
                print('数据库连接成功')
                break
            else:
                print('尝试连接数据库%s次失败' % (i+1))
        else:
            raise TimeoutError  # 3次都不成功则主动抛异常
    
    e = Event()
    Thread(target=connect, args=(e,)).start()
    Thread(target=check, args=(e,)).start()
    示例

    四、条件

    使得线程等待,只有满足某条件时,才释放n个线程

    Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。
    线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。
    不断的重复这一过程,从而解决复杂的同步问题。

    代码说明:

    from threading import Condition, Thread
    
    def func(con, index):
        print('%s在等待' % index)
        con.acquire()
        con.wait()
        print('%s do something' % index)
        con.release()
    
    con = Condition()
    for i in range(10):
        Thread(target=func, args=(con, i)).start()
    # con.acquire()
    # con.notify_all()
    # con.release()
    count = 10
    while count > 0:
        num = int(input('>>>'))
        con.acquire()
        con.notify(num)
        count -= num
        con.release()

    五、定时器

    定时器,指定n秒后执行某个操作

    from threading import Timer
    
    def func():
        print('执行我啦')
    
    Timer(5, func).start()
    print('主线程')

    六、队列

    queue队列 :使用import queue,用法与进程Queue一样

    queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

    class queue.Queue(maxsize=0)   # 先进先出
    import queue
    
    q=queue.Queue()
    q.put('first')
    q.put('second')
    q.put('third')
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(先进先出):
    first
    second
    third
    '''

    class queue.LifoQueue(maxsize=0)   # 后进先出

    import queue
    
    q=queue.LifoQueue()
    q.put('first')
    q.put('second')
    q.put('third')
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(后进先出):
    third
    second
    first
    '''

    class queue.PriorityQueue(maxsize=0)   # 存储数据时可设置优先级的队列

    import queue
    
    q=queue.PriorityQueue()
    # put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
    q.put((20,'a'))
    q.put((10,'b'))
    q.put((30,'c'))
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(数字越小优先级越高,优先级高的优先出队):
    (10, 'b')
    (20, 'a')
    (30, 'c')
    '''
  • 相关阅读:
    win10安装.net3.5
    VS2015密钥
    wordpress目录文件结构说明
    js | javascript获取和设置元素的属性
    wordpress | WP Mail SMTP使用QQ邮箱发布失败的解决办法
    jquery 实时监听输入框值变化方法
    XPath编写规则学习
    如何将portfolio产品图片上的悬停去掉?
    wordpress怎么禁止文章复制
    js | javascript实现浏览器窗口大小被改变时触发事件的方法
  • 原文地址:https://www.cnblogs.com/zyling/p/12789994.html
Copyright © 2020-2023  润新知