• Day36 python基础--并发编程基础5


    一,线程的锁

      Q1:线程为什么要有锁

        1.线程之间的数据安全问题:

          +=,-=,*=,/=赋值操作不安全,如果涉及这些这些一定要加锁

        2.pop.append是线程安全

        3.队列也是数据安全的

        4.多线程中,别在线程中操作全局变量

        5.可以使用dic模块中的方法来查看某个操作对应的cpu指令

      

      互斥锁与递归锁

    #互斥锁Lock
    from threading import Thread,Lock
    n = 1500000
    def func(lock):
        global n
        for i in range(1500000):
            with lock:   #如果不加锁,则会出现数据不安全的问题
                n -= 1
    
    def func2(lcok):
        global n
        for i in range(1500000):
            lock.acquire()   #如果不加锁,则会出现数据不安全的问题
            n += 1
            lock.release()
    
    if __name__ == '__main__':
        t_lst = []
        lock = Lock()
        for i in range(10):
            t = Thread(target=func,args=(lock,))
            t2 = Thread(target=func2,args=(lock,))
            t.start()
            t2.start()
            t_lst.append(t)
            t_lst.append(t2)
        for t in t_lst:
            t.join()
        print('-->',n)
    #互斥锁的死锁现象:
        #一共有两把锁
        #线程之间是异步的
            #操作的时候,抢到一把锁之后还要再去抢第二把锁
            #一个线程抢到一把锁
            #另一个线程抢到了另一把锁
    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_list = ['alex','wusir']
        name_list2 = ['nezha','yuan']
        for name in name_list:
            Thread(target=eat1,args=(name,)).start()
        for name in name_list2:
            Thread(target=eat2,args=(name,)).start()

      递归锁:

        1.递归锁可以解决互斥锁的死锁问题

          当多个线程抢占资源时,使用一把递归锁,解决死锁问题

        2.递归锁的优缺点:

          递归锁并不是一个好的解决方法

          死锁现象的发生不是互斥锁的问题,而是程序员的逻辑问题导致的

          递归锁能够快速的解决死锁问题

        3.递归锁的特点:

          当程序发生死锁的时候,能够迅速恢复服务,使用递归锁替换互斥锁

          在后续修复中,逐步把递归锁替换成互斥锁:完善代码逻辑,提高代码的效率

        4.递归锁的逻辑:

          多个线程之间,一个线程用完一个资源再到另外一个线程取使用

          先释放一个资源,再去获取一个资源的锁    

    #递归锁RLock
    from threading import Thread,RLock
    rlock = RLock()
    def func(name):
        rlock.acquire()
        print(name,1)
        rlock.acquire()
        print(name,2)
        rlock.acquire()
        print(name,3)
        rlock.acquire()
        print(name,4)
        rlock.release()
        rlock.release()
        rlock.release()
        rlock.release()
    if __name__ == '__main__':
        for i in range(10):
            Thread(target=func,args=('name%s'%i,)).start()
    #科学家吃面问题,把两把互斥锁改为一把递归锁修复死锁问题
    import time
    from threading import Thread,RLock
    # noodle_fork_lock = Lock()
    noodle_lock = fork_lock = RLock()
    def eat1(name):
        # noodle_fork_lock.acquire()
        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()
        # noodle_fork_lock.release()
        print('%s放下面'%name)
        
    def eat2(name):
        # noodle_fork_lock.acquire()
        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()
        # noodle_fork_lock.release()
        print('%s放下叉子' % name)
    
    if __name__ == '__main__':
        name_list = ['alex','wusir']
        name_list2 = ['nezha','yuan']
        for name in name_list:
            Thread(target=eat1,args=(name,)).start()
        for name in name_list2:
            Thread(target=eat2,args=(name,)).start()

    二,线程的信号量

      原理为:锁+计数器

      1.并没有减少线程需要的并发数量

      2.造成多个线程在等待资源的释放

    #信号量Semaphore
    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()

    三,线程的事件

      线程与线程之间同步执行,一个线程等待wait()的信号,并由信号决定,线程释放执行,另一个线程负责决定信号的状态,主要用于防止线程长时间或一直处于阻塞状态

    #事件:Event

    #
    实例:检测数据库连接 #先来测试一下数据库是否能被连接 #网通不通 #用户名,密码是否正确 #wait()等待 事件内的信号变成True #set() 把信号变成True #clear() 把信号变成False #is_set() 查看信号状态是否为True import time import random 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(timeout=0.5) if e.is_set(): print('数据库连接成功') break else: print('尝试连接数据库%s次失败'%(i+1)) else: raise TimeoutError #若超过连接次数,则主动抛出异常

    if __name__ == '__main__': e = Event() Thread(target=check,args=(e,)).start() Thread(target=connect,args=(e,)).start()

    四,线程的条件

      1.notify:控制流量,通知有多少人可以通过了,即设置多少个线程可执行,在使用前后都需要加锁

      2.wait:在门口等待的所有人,即处于阻塞状态的线程,使用前后都需要加锁

    #条件:Condition
    from threading import Condition,Thread
    def func(con,index):
        print('%s在等待'%index)
        con.acquire()
        print('%s在wait' % index)
        con.wait()
        print('% sdo something'%index)
        con.release()
    
    if __name__ == '__main__':
        con = Condition()
        for i in range(10):
            t = Thread(target=func,args=(con,i))
            t.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()

    五,线程的定时器

      1.子线程延迟执行不影响主程序的运行

      2.可以实现 linux操作系统上的定时任务的工具 crontab

    #定时器:Timer
    from threading import Timer
    def func():
        print('执行我啦')
    t = Timer(5,func)  #延迟5秒执行
    t.start()
    print('主线程')

    六,线程的queue

      1.队列:queue

        线程安全,一般用于排队相关逻辑

        保证qps每秒钟接收请求数

        帮助维护程序的响应的顺序

      应用场景,服务器响应大量并发连接,将来不及响应的连接,存入队列中,并依次响应

    #队列:queue
    import queue
    q = queue.Queue()
    #队列的方法
    q.put()
    q.get()
    q.put_nowait()
    q.get_nowait()
    q.full() #不准确
    q.empty() #不准确

      2.栈:LifoQueue

        维护先进后出的顺序

        应用场景:完成算法

    #栈:LifoQueue
    from queue import LifoQueue
    lq = LifoQueue()
    #栈的方法
    lq.put()
    lq.get()
    #实例:线程中的生产者消费者模型
    from threading import Thread
    import queue
    import time
    
    def consumer(q,name):
        while True:
            ret = q.get()
            if ret is None:break
            print(name,'吃了',ret)
    
    def producer(q,name,food):
        for i in range(10):
            time.sleep(0.1)
            q.put(food+str(i))
            print(name,'生产了',food+str(i))
    
    if __name__ == '__main__':
        q1 = queue.Queue()   #用于线程间的IPC
        c = Thread(target=consumer,args=(q1,'wusir'))
        p = Thread(target=producer,args=(q1,'alex','饭菜'))
        c.start()
        p.start()
        p.join()
        q1.put(None)

      3.优先级队列:PriorityQueue

        按照优先级条件决定谁先出队列

        队列中元素,数据类型需要一致,并且能狗满足比较大小的条件,否则报错

    #优先级队列:PriorityQueue
    from
    queue import PriorityQueue pq = PriorityQueue() pq.put((15,'abc')) pq.put((12,'def')) #优先级按第二个元素的第一个字母的ascii码大小 pq.put((12,'aaa')) #当第一个元素优先级一样是 pq.put((5,'ghi')) # pq.put(5) # pq.put(12) # pq.put(15) print(pq.get()) print(pq.get()) print(pq.get())

    七、总结

    # 线程安全的问题
    # 数据类型是否线程安全
    # += -=不安全
    # set list dict 基础方法线程安全
    #
    # 某块数据安全
    #     logging模块 是线程安全的
    #
    # 互斥锁和递归锁之间的区别
    #     出现死锁问题的本质
    #         两把锁,锁两个资源
    #         分别被不同线程抢占,导致死锁
    #     解决方法;
    #         速度上
    #         逻辑上
    # 线程中用到的模块
    #     锁,互斥锁,递归锁
    #     信号量
    #     事件
    #     队列

    八,线程中local的概念

      多个线程之间使用threading.local对象,可以实现多个线程之间的数据隔离

    import time
    import random
    from threading import local,Thread
    
    loc = local()
    def func2():
        global loc
        print(loc.name,loc.age)
    
    
    def func(name,age):
        global loc
        loc.name = name
        loc.age = age
        time.sleep(random.random())
        func2()
    
    Thread(target=func,args=('alex',40)).start()
    Thread(target=func,args=('boss_jin',38)).start()
  • 相关阅读:
    第一次作业 —— 【作业7】问卷调查
    讲座观后感
    学习进度表(随缘更新)
    数据结构与算法思维导图
    作业七问卷调查
    《创新者的逆袭,用第一性原理做颠覆式创新》读后感
    结对项目--四则运算生成器(Java) 刘彦享+龙俊健
    个人项目---WordCount实现(Java)
    自我介绍+软工五问
    简洁又快速地处理集合——Java8 Stream(下)
  • 原文地址:https://www.cnblogs.com/lianyeah/p/9700171.html
Copyright © 2020-2023  润新知