• 并发编程之理论


    死锁与递归锁

    死锁

    """
    定义:
    是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
    """
    
    from threading import Thread, Lock
    import time
    
    
    mutexA = Lock()
    mutexB = Lock()
    # 类只要加括号多次 产生的肯定是不同的对象
    # 如果你想要实现多次加括号等到的是相同的对象 单例模式
    
    
    class MyThead(Thread):
        def run(self):
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print('%s 抢到A锁'% self.name)  # 获取当前线程名
            mutexB.acquire()
            print('%s 抢到B锁'% self.name)
            mutexB.release()
            mutexA.release()
            
        def func2(self):
            mutexB.acquire()
            print('%s 抢到B锁'% self.name)
            time.sleep(2)
            mutexA.acquire()
            print('%s 抢到A锁'% self.name)  # 获取当前线程名
            mutexA.release()
            mutexB.release()
    
    
    if __name__ == '__main__':
        for i in range(10):
            t = MyThead()
            t.start()

    解决方案:递归锁

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

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

    """
    mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,
    这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
    """

    信号量

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

    from threading import Thread, Semaphore
    import threading
    import random
    import time
    
    sm = Semaphore(5)
    
    
    def happy_time(name):
        sm.acquire()
        print(f'任务号:[{threading.current_thread().getName()},{name}正在浪!]')
        time.sleep(random.randrange(1, 3))
        sm.release()
    
    
    if __name__ == '__main__':
        player = ['alex', 'egon', 'tank', 'jason', 'jim', 'pei', 'jack', 'surpass', 'tom', 'allen']
        for i in range(10):
            t = Thread(target=happy_time, args=(player[i],))
            t.start()

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

    Event事件

    """
    线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,
    这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,
    它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,
    那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。
    如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
    """ """ event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。 """ from threading import Thread, Event import threading import time event = Event() # 创造一个红绿灯 def light(): print('红灯亮着!') time.sleep(3) print('绿灯亮了!') event.set() def car(name): print(f'[{threading.current_thread().getName()}]:{name}正在等红灯') event.wait() print(f'{name}狂踩油门,冲出去了!') if __name__ == '__main__': t1 = Thread(target=light) t1.start() car_list = ['宝马', '奔驰', '奥迪', '法拉利', '兰博基尼', '保时捷', '宾利'] for i in range(7): t2 = Thread(target=car, args=(car_list[i],)) t2.start()

    线程Q

    """
    同一个进程下多个线程数据是共享的
    为什么先同一个进程下还会去使用队列呢
    因为队列是
        管道 + 锁
    所以用队列还是为了保证数据的安全
    """
    
    import queue
    
    # 我们现在使用的队列都是只能在本地测试使用
    
    # 1 队列q  先进先出
    # q = queue.Queue(3)
    # q.put(1)
    # q.get()
    # q.get_nowait()
    # q.get(timeout=3)
    # q.full()
    # q.empty()
    
    
    # 后进先出q
    # q = queue.LifoQueue(3)  # last in first out
    # q.put(1)
    # q.put(2)
    # q.put(3)
    # print(q.get())  # 3
    
    # 优先级q   你可以给放入队列中的数据设置进出的优先级
    q = queue.PriorityQueue(4)
    q.put((10, '111'))
    q.put((100, '222'))
    q.put((0, '333'))
    q.put((-5, '444'))
    print(q.get())  # (-5, '444')
    # put括号内放一个元祖  第一个放数字表示优先级
    # 需要注意的是 数字越小优先级越高!!!

    进程池与线程池

    """
    无论是开设进程也好还是开设线程也好 是不是都需要消耗资源
    只不过开设线程的消耗比开设进程的稍微小一点而已
    
    我们是不可能做到无限制的开设进程和线程的 因为计算机硬件的资源更不上!!!
    硬件的开发速度远远赶不上软件呐
    
    我们的宗旨应该是在保证计算机硬件能够正常工作的情况下最大限度的利用它
    """
    # 池的概念
    """
    什么是池?
    	池是用来保证计算机硬件安全的情况下最大限度的利用计算机
    	它降低了程序的运行效率但是保证了计算机硬件的安全 从而让你写的程序能够正常运行
    """
    
    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>
            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 = ProcessPoolExecutor(5)
    pool.submit(task, i).add_done_callback(call_back)

    协程

    """
    进程:资源单位
    线程:执行单位
    协程:这个概念完全是程序员自己意淫出来的 根本不存在
    		单线程下实现并发
    		我们程序员自己再代码层面上检测我们所有的IO操作
    		一旦遇到IO了 我们在代码级别完成切换
    		这样给CPU的感觉是你这个程序一直在运行 没有IO
    		从而提升程序的运行效率
    	
    多道技术
    	切换+保存状态
    	CPU两种切换
    		1.程序遇到IO
    		2.程序长时间占用
    
    TCP服务端 
    	accept
    	recv
    	
    代码如何做到
    	切换+保存状态
    
    切换
    	切换不一定是提升效率 也有可能是降低效率
    	IO切			提升
    	没有IO切 降低
    		
    保存状态
    	保存上一次我执行的状态 下一次来接着上一次的操作继续往后执行
    	yield
    """
    

    验证切换是否能够提升效率

    """
    计算密集型:降低效率
    I/O密集型:提高效率
    """
    

    gevnet模块

    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()

    总结:

    """
    理想状态:
        我们可以通过
        多进程下面开设多线程
        多线程下面再开设协程序
        从而使我们的程序执行效率提升
    """
  • 相关阅读:
    为什么不要用VSCODE来写Makefile
    JavaFX第三弹
    javaFX文件和文件夹选择器
    写了一个vsftpd的GUI
    在java中调用shell命令和执行shell脚本
    正交投影与斯密特正交化的好处
    Linux下安装软件
    C++中的仿函数
    C++中重载操作符[ ]
    使用斐波那契查找
  • 原文地址:https://www.cnblogs.com/zhenghuiwen/p/12789844.html
Copyright © 2020-2023  润新知