• day41——死锁、信号量、进程线程池、协程


    今日内容:

    1. 死锁于递归锁
    2. 信号量
    3. Event事件
    4. 线程q
    5. 进程池与线程池
    6. 协程
    7. 协程实现TCP服务端的并发效果

    1 死锁与递归锁

    锁要尽量少用,容易造成死锁,就算知道我们知道如何使用抢锁和释放锁,但在操作锁的同时还是容易产生死锁现象,将程序阻塞住。

    例如:

    1)死锁:

    from threading import Thread, Lock
    import time
    
    mutexA = Lock()
    mutexB = Lock()
    
    
    class MyThread(Thread):
        def run(self):
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print(f'{self.name}抢到了A锁')  # 获取当前线程名
            mutexB.acquire()
            print(f'{self.name}抢到了B锁')
            mutexB.release()
            mutexA.release()
    
        def func2(self):
            mutexB.acquire()
            print(f'{self.name}抢到了B锁')  # 获取当前线程名
            time.sleep(3)
            mutexA.acquire()
            print(f'{self.name}抢到了A锁')
            mutexA.release()
            mutexB.release()
    
    
    if __name__ == '__main__':
        for i in range(10):
            t = MyThread()
            t.start()
    
    '''
    Thread-1抢到了A锁
    Thread-1抢到了B锁
    Thread-1抢到了B锁
    Thread-2抢到了A锁'''
    
    '''
    阻塞原因:线程1第二次抢A锁时,A锁在线程2手上,而线程2第一次抢B锁时,
    B锁在线程1手上,相互的抢不着,就好像两个人都被锁在了各自的房间里,
    手里拿着的却是别人房间的钥匙。
    '''
    

    2)递归锁:

    from threading import Thread, RLock
    import time
    
    mutexA = mutexB = RLock()
    
    
    class MyThread(Thread):
        def run(self):
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print(f'{self.name}抢到了A锁')  # 获取当前线程名
            mutexB.acquire()
            print(f'{self.name}抢到了B锁')
            mutexB.release()
            mutexA.release()
    
        def func2(self):
            mutexB.acquire()
            print(f'{self.name}抢到了B锁')  # 获取当前线程名
            time.sleep(3)
            mutexA.acquire()
            print(f'{self.name}抢到了A锁')
            mutexA.release()
            mutexB.release()
    
    
    if __name__ == '__main__':
        for i in range(10):
            t = MyThread()
            t.start()
    
    # 每一个线程都能抢到A锁和B锁两次。
    

    递归锁的特点:

    • 可以被连续的acquire和release。

    • 只能被第一个抢到这把锁的执行上面的操作

    • 跟JoinableQueue类似,递归锁内部有一个计数器,每acquire一次计数加一,

      release一次计数减一。

    • 只要计数不为0,其他的都无法抢到该锁。

    举个悲伤的例子:你跟你女神表白,你女神已经有了现任的男朋友,她男朋友送一次口红,他们的亲密度加一,吵一次架亲密度减一。但只有在他们的亲密度为零也就是真正分手了,你才有机会。

    2 信号量

    信号量在不同的阶段对应不同的技术点,在我们现在学的并发编程中信号量指的是锁。

    from threading import Thread, Semaphore
    import time
    import random
    
    
    """
    如果我们将 互斥锁比喻成一个厕所的话,
    那信号量就好比于公共厕所,它有多个坑位(对应有多个锁可以抢)"""
    
    ms = Semaphore(5)   # 括号内的数字表示开设的坑位
    
    
    def task(name):
        ms.acquire()
        print(f'{name} 正在蹲坑')
        time.sleep(random.randint(1,5))
        ms.release()
    
    if __name__ == '__main__':
        for i in range(20):
            t=Thread(target=task,args=(f'大帅比{i}',))
            t.start()
    

    3 Event事件

    一些线程或者进程在另外一些线程或者进程执行完毕后才能运行,类似于发射信号。

    from threading import Thread, Event
    import time
    
    event = Event()  # 造了一个红绿灯
    
    
    def light():
        print('红灯亮着的')
        time.sleep(3)
        print('绿灯亮着的')  # 等红灯的人可以通行了
        event.set()
    
    
    def car(name):
        print(f'{name} 车正在等红灯')
        event.wait()  # 等待别人给你发信号
        print(f'{name} 车开走了')
    
    
    if __name__ == '__main__':
        t = Thread(target=light)
        t.start()
    
        for i in range(20):
            t1 = Thread(target=car, args=(i,))
            t1.start()
    

    4 线程q

    进程的数据,进程下的多个线程是可以共享的,之所以还要使用队列,是因为队列等同于管道加锁,保证了数据的安全。

    # 1 队列q 先进先出
    # 基本方法
    q = queue.Queue(3)
    q.put(1)
    q.get()
    q.get_nowait()
    q.get(timeout=3)
    q.full()
    q.empty()
    
    
    # 2 堆栈q 先进后出q
    q = queue.LifoQueue(3)  # last in first out
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.get())  # 3
    
    
    # 3 优先级q  
    q = queue.PriorityQueue(4)
    q.put((2,'222'))
    q.put((1,'111'))
    q.put((0,'000'))
    q.put((-1,'-111'))
    
    print(q.get())  # (-1, '-111')
    print(q.get())  # (0, '000')
    
    # 往队列中存放数据的时候可以设置进出的优先级,
    # 以元组的形式传参,第一个元素为数字表示的是存放的第二元素的进出优先级,
    # 数字越小优先即优先级越高,优先级:负数>0>正数
    

    ps:我们目前使用的队列都是只能在本地测试使用

    5 进程池与线程池

    线程进程实现TCP服务端并发是将通信循环交给了线程或者进程去处理,无论是开始进程还是线程都是消耗资源的,只不过线程开销相对小一点,但假如我们有十万百万个客服端,我们不可能无限制的去开进程或者线程,因为计算机的硬件资源更不上,硬件的开发速度远远赶不上软件。

    我们的目的在保证计算机硬件能正常工作的情况下,尽可能的去利用它

    池的概率:

    是用来保证计算机硬件安全的情况下最大限度的利用计算机,他降低了程序运行的效率但是保证了计算机硬件的安全,从而保证程序能够正常运行。

    基本使用:

    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    import time
    import os
    
    
    # pool = ThreadPoolExecutor(5)  # 池子里面固定只有五个线程
    # 括号内可以传数字 不传的话默认会开设当前计算机cpu个数五倍的线程
    pool = ProcessPoolExecutor(5)
    # 括号内可以传数字 不传的话默认会开设当前计算机cpu个数进程
    """
    池子造出来之后 里面会固定存在五个线程
    这个五个线程不会出现重复创建和销毁的过程
    池子造出来之后 里面会固定的几个进程
    这个几个进程不会出现重复创建和销毁的过程
    
    池子的使用非常的简单
    你只需要将需要做的任务往池子中提交即可 自动会有人来服务你
    """
    
    
    def task(n):
        print(n,os.getpid())
        time.sleep(2)
        return n**n
    
    def call_back(n):
        print('call_back>>>:',n.result())
    """
    任务的提交方式
        同步:提交任务之后原地等待任务的返回结果 期间不做任何事
        异步:提交任务之后不等待任务的返回结果 执行继续往下执行
            返回结果如何获取???
            异步提交任务的返回结果 应该通过回调机制来获取
            回调机制
                就相当于给每个异步任务绑定了一个定时炸弹
                一旦该任务有结果立刻触发爆炸
    """
    if __name__ == '__main__':
        # pool.submit(task, 1)  # 朝池子中提交任务  异步提交
        # print('主')
        t_list = []
        for i in range(20):  # 朝池子中提交20个任务
            # res = pool.submit(task, i)  # <Future at 0x100f97b38 state=running>
            res = pool.submit(task, i).add_done_callback(call_back)
            # print(res.result())  # result方法   同步提交
            # t_list.append(res)
        # 等待线程池中所有的任务执行完毕之后再继续往下执行
        # pool.shutdown()  # 关闭线程池  等待线程池中所有的任务运行完毕
        # for t in t_list:
        #     print('>>>:',t.result())  # 肯定是有序的
    """
    程序有并发变成了串行
    任务的为什么打印的是None
    res.result() 拿到的就是异步提交的任务的返回结果
    

    需要重点掌握的代码:

    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    pool=ThreadPoolExecutor
    pool1=ProcessPoolExecutor
    pool.submit(task,i).add_done_callback(call_back)
    

    6 协程

    """
    进程:资源单位
    线程:执行单位
    协程:这个概念完全是程序员自己意淫出来的 根本不存在
    		单线程下实现并发
    		我们程序员自己再代码层面上检测我们所有的IO操作
    		一旦遇到IO了 我们在代码级别完成切换
    		这样给CPU的感觉是你这个程序一直在运行 没有IO
    		从而提升程序的运行效率
    	
    多道技术
    	切换+保存状态
    	CPU两种切换
    		1.程序遇到IO
    		2.程序长时间占用
    
    TCP服务端 
    	accept
    	recv
    	
    代码如何做到
    	切换+保存状态
    
    切换
    	切换不一定是提升效率 也有可能是降低效率
    	IO切			提升
    	没有IO切 降低
    		
    保存状态
    	保存上一次我执行的状态 下一次来接着上一次的操作继续往后执行
    	yield
    """
    
    from gevent import monkey;monkey.patch_all()
    import time
    from gevent import spawn
    
    """
    gevent模块本身无法检测常见的一些io操作
    在使用的时候需要你额外的导入一句话
    from gevent import monkey
    monkey.patch_all()
    又由于上面的两句话在使用gevent模块的时候是肯定要导入的
    所以还支持简写
    from gevent import monkey;monkey.patch_all()
    """
    
    
    def heng():
        print('哼')
        time.sleep(2)
        print('哼')
    
    
    def ha():
        print('哈')
        time.sleep(3)
        print('哈')
    
    def heiheihei():
        print('heiheihei')
        time.sleep(5)
        print('heiheihei')
    
    
    start_time = time.time()
    g1 = spawn(heng)
    g2 = spawn(ha)
    g3 = spawn(heiheihei)
    g1.join()
    g2.join()  # 等待被检测的任务执行完毕 再往后继续执行
    g3.join()
    # heng()
    # ha()
    # print(time.time() - start_time)  # 5.005702018737793
    print(time.time() - start_time)  # 3.004199981689453   5.005439043045044
    

    协程实现TCP服务端的并发

    # 服务端
    from gevent import monkey;monkey.patch_all()
    import socket
    from gevent import spawn
    
    
    def communication(conn):
        while True:
            try:
                data = conn.recv(1024)
                if len(data) == 0: break
                conn.send(data.upper())
            except ConnectionResetError as e:
                print(e)
                break
        conn.close()
    
    
    def server(ip, port):
        server = socket.socket()
        server.bind((ip, port))
        server.listen(5)
        while True:
            conn, addr = server.accept()
            spawn(communication, conn)
    
    
    if __name__ == '__main__':
        g1 = spawn(server, '127.0.0.1', 8080)
        g1.join()
    
        
    # 客户端
    from threading import Thread, current_thread
    import socket
    
    
    def x_client():
        client = socket.socket()
        client.connect(('127.0.0.1',8080))
        n = 0
        while True:
            msg = '%s say hello %s'%(current_thread().name,n)
            n += 1
            client.send(msg.encode('utf-8'))
            data = client.recv(1024)
            print(data.decode('utf-8'))
    
    
    if __name__ == '__main__':
        for i in range(500):
            t = Thread(target=x_client)
            t.start()
    

    利用CPU最理想的状态:多进程下开多线程,多线程序再开协程

  • 相关阅读:
    vue使用laydate.js插件报错laydate.css: Invalid
    自定义css样式结合js控制audio做音乐播放器
    福利福利~262集前端免费视频!
    解决Vue在IE中报错出现不支持=>等ES6语法和“Promise”未定义等问题
    设置Chart.js默认显示Point点的值不用鼠标经过才显示
    js监听某个元素高度变化来改变父级iframe的高度
    Vue中注意target和currentTarget的使用
    VUE中让由全局变量添加生成的新数组不随全局变量的变化而变化
    bootstrap-table前端实现多条件时间段查询数据
    js小数点相乘或相除出现多位数的问题
  • 原文地址:https://www.cnblogs.com/zhangtieshan/p/12791199.html
Copyright © 2020-2023  润新知