• 生产者消费者模型、线程、守护线程和死锁现象


      今天学习了生产者消费者模型、线程、守护线程和死锁现象。

      一、生产者消费者模型

        什么是模型: 一种编程套路

        生产者指的是能够产生数据的一类任务

        消费者指的是处理数据的一类任务

      生产者消费者模型为什么出现?

      生产者的处理能力与消费者的处理能力 不匹配不平衡 导致了一方等待另一方 浪费时间

      目前我们通过多进程将生产 和 消费 分开处理

      然后将生产者生产的数据通过队列交给消费者

      总结一下在生产者消费者模型中 不仅需要生产者消费者 还需要一个共享数据区域

      1、将生产方和消费方耦合度降低

      2、平衡双方的能力 提高整体效率

      代码实现:

        搞两个进程 一个负责生产 一个负责消费

        数据需要共享所以来个队列

      我们现在以王思聪和他的小伙伴为消费者 然后提供三个能够生产热狗的店来举个例子:~

      

    import time,random
    from multiprocessing import Process,JoinableQueue
    # 制作热狗
    def make_hotdog(queue,name):
        for i in range(3):
            time.sleep(random.randint(1,2))
            print('%s 制作了一个热狗 %s'%(name,i))
            # 生产得到的数据
            data = '%s生产的热狗%s'%(name,i)
            # 存到队列中
            queue.put(data)
     
    #吃热狗
    def eat_hotdog(queue,name):
        while True:
            data = queue.get()
            time.sleep(random.randint(1,2))
            print('%s 吃了%s'%(name,data))
            # 该函数就是用来记录一共给消费方多少数据了 就是get次数
            queue.task_done()
    
    if __name__ == '__main__':
        # 创建队列
        q = JoinableQueue()
        p1 = Process(target = make_hotdog,args = (q,'Zero的热狗店'))
        p2 = Process(target = make_hotdog,args = (q,'Egon的热狗店'))
        p3 = Process(target = make_hotdog,args = (q,'Alex的热狗店'))
        c1 = Process(target = eat_hotdog,args = (q,'张胖'))
        c2 = Process(target = eat_hotdog,args = (q,'王思聪'))
        p1.start()
        p2.start()
        p3.start()
        c1.daemon = True
        c2.daemon = True
        c1.start()
        c2.start()
        # 让主进程等三家店全都做完后....
        p1.join()
        p2.join()
        p3.join()
        # 主进程等到队列结束时再继续  那队列什么时候算结束? 生产者已经生产完了 并且消费者把数据全取完了
        q.join()
        print('主进程over')

      二、线程

      threading模块,和multiprocessing模块在使用层面有很大的相似性。

      开启线程的两种方式:

      

    # 方式一
    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello'%name)
    
    if __name__ == '__main__':
        t = Thread(target = sayhi,args = ('zero',))
        t.start()
        print('主线程')
    #方式二
    from threading import Thread
    import time
    class Sayhi(Thread):
        def __init__(self,name):
            super().__init__()
            self.name = name
        def run(self):
            time.sleep(2)
            print('%s say hello' %self.name)
    
    if __name__ == '__main__'
        t = Sayhi('zero')
        t.start()
        print('主线程')

      在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

      

    from threading import Thread
    from multiprocessing import Process
    import os
    
    def work():
        print('hello')
    
    if __name__=='__main__':
        #在主进程下开启线程
        t = Thread(target = work)
        t.start()
        print('主线程/主进程')
        '''
        打印结果:
        hello
        主线程/主进程
        '''
    from threading import Thread
    from multiprocessing import Process
    import os
    
    def work():
        print('hello',os.getpid())
    
    if __name__ == '__main__':
        #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
        t1 = Thread(target = work)
        t2 = Thread(target = work)
        t1.start()
        t2.start()
        print('主线程/主进程pid',os.getpid())
        #part2:开多个进程,每个进程都有不同的pid
        p1 = Process(target = work)
        p2 = Process(target = work)
        p1.start()
        p2.start()
        print('主线程/主进程pid',os.getpid())
    from thrading import Thread
    from multiprocessing import Process
    import os
    
    def work():
        global n 
        n = 0
    
    if __name__ == '__main__':
        #n = 100
        #p = Process(target = work)
        #p.start()
        #p.join()
        #print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
    
        n = 1
        t = Thread(target = work)
        t.start()
        t.join()
        print('',n)     #查看结果为0,因为同一进程内的线程之间共享进程内的数据

      线程相关的其他方法

      Thread实例对象的方法

        # isAlive(): 返回线程是否活动的。

        # getName(): 返回线程名。

        # setName(): 设置线程名。

      threading 模块提供的一些方法:

        # threading.currentThread(): 返回当前的线程变量

        # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

        # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

      

    from threading import Thread
    import threading
    from multiprocessing import Process
    import os
    
    def work():
        import time
        time.sleep(3)
        print(threading.current_thread().getName())
    
    if __name__ == '__main__':
        #在主进程下开启线程
        t = Thread(target = work)
        t.start()
        print(threading.current_thread().getName())
        print(threading.current_thread())  #主线程
        print(threading.enumerate())    #连同主线程内有两个运行的线程
        print('主线程/主进程')
    
        '''
        打印结果:
        MainThread
        <_MainThread(MainThread, started 1401234235214)>
        [<_MainThread(MainThread, started 1401234235214)>,<Thread(Thread -1, started 212315134512513)>]
        主线程/主进程
        Thread -1
        '''

      主线程等待子线程结束

      

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello'%name)
    
    if __name__ == '__main__':
        t = Thread(target=sayhi,args = ('zero',))
        t.start()
        t.join()
        print('主线程')
        print(t.is_alive)
        '''
        zero say hello
        主线程
        False
        '''    

      三、守护线程

      无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁

      需要强调的是:运行完毕并非终止运行

      1.对主进程来说,运行完毕指的是主进程代码运行完毕

      2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

      详细解释:

      1.主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。

      2.主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

      

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello'%name)
    
    if __name == '__main__':
        t = Thread(target = sayhi,args = ('zero',))
        t.setDaemon(True)     #必须在t.start()之前设置
        t.start()
        print('主线程')
        print(t.is_alive())
        '''
        主线程
        True
        '''   
    from threading import Thread
    import time
    def foo():
        print(123)
        time.sleep(1)
        print('end123')
    
    def bar():
        print(456)
        time.sleep(3)
        print('end456')
    
    t1 = Thread(target = foo)
    t2 = Thread(target = bar)
    t1.daemon = True
    t1.start()
    t2.start()
    print('main---------')
        

      四、死锁现象与递归锁

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

    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('%s拿到A锁'%self.name)
            mutexB.acquire()
            print('%s拿到B锁'%self.name)
            mutexB.release()
            murexA.release()
    
        def func2(self):
            mutexB.acquire()
            print('%s拿到B锁'%self.name)
            time.sleep(2)
            mutexA.acquire()
            print('%s拿到A锁'%self.name)
            mutexA.relrese()
            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锁
    然后就卡住,死锁了    
    '''

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

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

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

  • 相关阅读:
    [java][JEECG] Maven settings.xml JEECG项目初始化 RouYi settings.xml配置
    Deepin-TIM或Deepin-QQ调整界面DPI字体大小的方法
    deepin 深度Linux系统 15.11 链接蓝牙鼠标问题
    安装vs code之后,win+e快捷键打开的是vs code,而不是文件管理器,解决方法
    【golang】使用rpcx不指定tags报错 undefined: serverplugin.ConsulRegisterPlugin
    【Python】安装MySQLdb模块centos 6.1 宝塔Linux面板 MySQL5.6
    [Nginx]配置文件详解
    linux 下终端通过证书、私钥快捷登录
    npm 更新包
    golang 无缓冲channel
  • 原文地址:https://www.cnblogs.com/xiaocaiyang/p/9937138.html
Copyright © 2020-2023  润新知