zoukankan      html  css  js  c++  java
  • 并发编程02

    子进程回收的两种方式

    • join让主进程等待子进程结束后,并回收进程资源,主进程在结束并回收资源
    from multiprocessing import Process
    import time
    
    
    def task():
        print('start...')
        time.sleep(3)
        print('end...')
    
    
    if __name__ == '__main__':
        p_obj = Process(target=task)
        p_obj.start()
    
        p_obj.join()
        print('主进程结束....')
    
    • 主进程正常结束,子进程与主进程一并被回收资源
    from multiprocessing import Process
    import time
    
    
    def task():
        print('start...')
        time.sleep(3)
        print('end...')
    
    
    if __name__ == '__main__':
        p_obj = Process(target=task)
        p_obj.start()
        print('主进程结束....')
    

    僵尸进程

    在子进程结束后,主进程没有正常结束,子进程的PID不会被回收

    缺点:

    ​ ①:操作系统中的PID号都是有限的,如有子进程PID号无法正常回收,则会占用PID号

    ​ ②:PID号骂好了,则无法创建新的进程

    ​ ③:浪费资源

    孤儿进程(没有坏处)

    在子进程没有结束时,主进程没有正常结束,子进程PID不会被回收

    孤儿院:操作系统优化机制

    ​ 当主进程意外终止,操作系统会检测是否有正在运行的子进程,会将他们放入孤儿院中,让操作系统帮你自动回收

    守护进程

    当主进程结束时,子进程必须结束(陪葬机制)

    from multiprocessing import Process
    
    import time
    
    
    def dome(name):
        print(f'start....{name}')
        time.sleep(3)
        print(f'end...{name}')
        print('子进程结束...')
    
    
    if __name__ == '__main__':
        p = Process(target=dome, args=('太监一号', ))
    
        # 守护进程必须在p.start()调用前设置
        p.daemon = True
    
        p.start()
    
        time.sleep(1)
        print('皇帝驾崩啦啊~~~~~')
    
    # 打印结果:
    # start....太监一号
    # 皇帝驾崩啦啊~~~~~
    

    进程间数据是隔离的

    from multiprocessing import Process
    import time
    
    number = 10
    
    
    def func():
        global number
        number = 100
    
    
    def func1(number):
        number += 10
    
    
    if __name__ == '__main__':
        p_obj = Process(target=func)
        p_obj1 = Process(target=func1, args=(number,))
    
        p_obj.start()
        p_obj1.start()
        p_obj.join()
        p_obj1.join()
        time.sleep(1)
        print(number)   # 10 ---> 证明数据是隔离的
    

    进程互斥锁

    进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件或同一个打印终端是没有问题的,而共享带来的就是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理

    # 抢票例题
    from multiprocessing import Process
    from multiprocessing import Lock    # ---> 进程互斥锁
    import random
    import json
    import time
    
    
    # 抢票例子
    
    # 1、查看余票
    def search(name):
        # 1.读取data.json文件中的数据
        with open('data.json', 'r', encoding='utf-8')as f:
            data_dic = json.load(f)
            print(f'用户[{name}]查看余票,余票还剩[{data_dic.get("number")}]!')
    
    
    # 2、若有余票,购买成功,票数会减少
    def buy(name):
        # 网络延时
        with open('data.json', 'r', encoding='utf-8')as f:
            data_dic = json.load(f)
    
        # 进入这一步 证明最先抢到票
        if data_dic.get('number') > 0:
            data_dic['number'] -= 1
            time.sleep(random.randint(1, 3))
            with open('data.json', 'w', encoding='utf-8')as f:
                json.dump(data_dic, f)
            print(f'用户[{name}],抢票成功!')
    
        else:
            print(f'用户[{name}],抢票失败!')
    
    
    def run(name, lock):
        search(name)
    
        lock.acquire()      # 加锁
    
        buy(name)
    
        lock.release()      # 释放锁
    
    
    if __name__ == '__main__':
        lock = Lock()
        # 开启多进程,实现并发
        for line in range(10):
            p_obj = Process(target=run, args=(f'张全蛋{line}', lock))
            p_obj.start()
    

    总结:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,速度是慢了,但牺牲了效率却保证了数据安全,这就是互斥锁的特点

    队列(遵循先进先出)

    队列相当于一个第三方的管道,可以存放数据,基于队列,可以实现进程间的互相通信

    创建队列有三种方式

    from multiprocessing import Queue
    from multiprocessing import JoinableQueue
    import queue
    # 第一种
    # Queue(5)指的是队列中只能存放5份数据
    q_obj = Queue(5)       # q_obj队列的对象
    # 添加数据队列中
    q_obj.put('张三')     # put:添加数据到队列中,将队列加满会阻塞
    print('添第加1个')
    q_obj.put('李四')
    print('添加第2个')
    q_obj.put('王二麻子')
    print('添加第3个')
    q_obj.put('张全蛋')
    print('添加第4个')
    q_obj.put('李小花')
    print('添加第5个')
    # q_obj.put('王小明')    # 到这里程序就会进入阻塞态
    # print('添加第6个')
    
    print(q_obj.get())      # 取出队列中的数据,取完再取会阻塞
    print(q_obj.get())
    print(q_obj.get())
    print(q_obj.get())
    print(q_obj.get())
    # print(q_obj.get())    # 到这里程序就会进入阻塞态
    
    
    
    # # 第二种
    # JoinableQueue(5)指的是队列中只能存放5份数据
    q_obj = JoinableQueue(5)       # q_obj队列的对象
    # 添加数据队列中
    q_obj.put('张三')     # put:添加数据到队列中,将队列加满会阻塞
    print('添第加1个')
    q_obj.put('李四')
    print('添加第2个')
    q_obj.put('王二麻子')
    print('添加第3个')
    q_obj.put('张全蛋')
    print('添加第4个')
    q_obj.put('李小花')
    print('添加第5个')
    # q_obj.put('王小明')    # 到这里程序就会进入阻塞态
    # print('添加第6个')
    
    print(q_obj.get())      # 取出队列中的数据,取完再取会阻塞
    print(q_obj.get())
    print(q_obj.get())
    print(q_obj.get())
    print(q_obj.get())
    # print(q_obj.get())    # 到这里程序就会进入阻塞态
    
    
    
    # # 第三种
    # queue.Queue(5)指的是队列中只能存放5份数据
    q_obj = queue.Queue(5)       # q_obj队列的对象
    # 添加数据队列中
    q_obj.put('张三')     # put:添加数据到队列中,将队列加满会阻塞
    print('添第加1个')
    q_obj.put('李四')
    print('添加第2个')
    q_obj.put('王二麻子')
    print('添加第3个')
    q_obj.put('张全蛋')
    print('添加第4个')
    q_obj.put('李小花')
    print('添加第5个')
    # q_obj.put('王小明')    # 到这里程序就会进入阻塞态
    # print('添加第6个')
    
    print(q_obj.get())      # 取出队列中的数据,取完再取会阻塞
    print(q_obj.get())
    print(q_obj.get())
    print(q_obj.get())
    print(q_obj.get())
    # print(q_obj.get())    # 到这里程序就会进入阻塞态
    
    
    
    # 还有put和get的进阶用法
    q_obj.put_nowait('张三')     # put_nowait:添加数据到队列中,将队列加满会报错
    print('添加第1个')
    q_obj.put_nowait('李四')
    print('添加第2个')
    q_obj.put_nowait('王二麻子')
    print('添加第3个')
    q_obj.put_nowait('张全蛋')
    print('添加第4个')
    q_obj.put_nowait('李小花')
    print('添加第5个')
    q_obj.put_nowait('王小明')    # 到这里程序就会报错
    print('添加第6个')
    
    print(q_obj.get_nowait())      # 取出队列中的数据,取完再取会报错
    print(q_obj.get_nowait())
    print(q_obj.get_nowait())
    print(q_obj.get_nowait())
    print(q_obj.get_nowait())
    print(q_obj.get_nowait())       # # 到这里程序就会报错
    

    IPC机制(实现进程间通信)

    通过队列实现进程间的通信

    from multiprocessing import Process
    from multiprocessing import JoinableQueue
    import time
    
    
    def task1(q):
        x = 100
        q.put(x)
        print('添加数据')
        time.sleep(3)
        print(q.get())
    
    
    def task2(q):
        # 想要在task2中获取task1中的数据x
        res = q.get()
        print(f'获取的数据是[{res}]')
        q.put(9527)
    
    
    if __name__ == '__main__':
        # 产生队列
        q = JoinableQueue(10)
    
        # 产生两个不同的子进程
        p1 = Process(target=task1, args=(q, ))
        p2 = Process(target=task2, args=(q, ))
    
        p1.start()
        p2.start()
        
    # 打印结果:
    # 添加数据
    # 获取的数据是[100]
    # 9527
    

    生产者与消费者

    生产者:生产数据的

    消费者:使用数据的

    解决供需不平衡问题

    from multiprocessing import JoinableQueue
    from multiprocessing import Process
    import time
    
    
    # 生产者:生产数据 ---> 队列
    def producer(name, food, q):
        msg = f'{name}生产了{food}食物'
        # 生产一个食物,添加到队列中
        q.put(food)
        print(msg)
    
    
    # 消费者:使用数据  <--- 队列
    def customer(name, q):
        while True:
            try:
                time.sleep(0.5)
                # 若报错则跳出循环
                food = q.get_nowait()
                msg = f'{name}吃了{food}食物!'
                print(msg)
    
            except Exception:
                break
    
    
    if __name__ == '__main__':
        q = JoinableQueue()
    
        # 创建生产者生产10个食物
        for line in range(10):
            p1 = Process(target=producer, args=('tank', f'Pig饲料{line}', q))
            p1.start()
    
        # 创建两个消费者
        c1 = Process(target=customer, args=('Pig1', q))
        c2 = Process(target=customer, args=('Pig2', q))
    
        c1.start()
        c2.start()
    
    

    线程

    什么是线程?

    进程:资源单位

    线程:执行单位

    线程与进程都是虚拟的概念,为了更好表达某种事物

    PS:开启一个进程一定会自带一个线程,线程才是真正的执行者

    为什么要使用线程?

    节省资源的占用

    开启进程和开启线程的区别

    ​ 开启进程

    ​ ①:开辟一个名称空间,每开启一个进程都会占用一份内存资源

    ​ ②:会自带一个主线程

    ​ 开启线程

    ​ ①:一个进程可以开启多个线程,从进程的内存空间中申请执行单位

    ​ ②:节省内存资源

    打个比方:

    开启三个进程:占用三份内存空间

    开启三个线程,从一个内存资源中,申请三个小的执行单位

    PS:进程与进程之间数据都是隔离的,线程与线程之间的数据都是共享的

    创建线程的两种方式

    from multiprocessing import Process
    from threading import Thread
    import time
    # 方式一
    def task():
        print("线程start...")
        time.sleep(3)
        print("线程end...")
    
    
    if __name__ == '__main__':
        p_obj = Process(target=task)
        p_obj.start()
        p_obj.join()
        print("进程...")
    
    
    # # 方式二
    class MyThread(Thread):
        def __init__(self, name):
            super().__init__()
            self.name = name
    
        def run(self) -> None:
            print("线程start...")
            time.sleep(3)
            print("线程end...")
    
    
    if __name__ == '__main__':
        p = MyThread("线程")
        p.start()
        p.join()
        print("主线程")
    

    线程互斥锁(和进程互斥锁一样使用)

    from threading import Lock
    from threading import Thread
    import time
    lock = Lock()
    
    # 开启10个线程,对一个数据进行修改
    number = 100
    
    
    def task():
        global number
    
        lock.acquire()
        number2 = number
        # time.sleep(1)
        number = number2 - 1
        lock.release()
    
    
    if __name__ == '__main__':
    
        list1 = []
        for line in range(10):
            t = Thread(target=task)
            t.start()
            list1.append(t)
    
        for t in list1:
            t.join()
    
        print(number)  # 90
    

    线程池

    他是基于concurrent.futures模块,用来限制创建的线程数量的

    # 线程池限制线程数量
    from concurrent.futures import ThreadPoolExecutor
    pool = ThreadPoolExecutor(50)  # 限制线程数量为50
    
    
    def task():
        import time
        time.sleep(1)
    
    
    while True:
        pool.submit(task)  # 相当于Thread().start()
    

    GIL全局解释器锁(纯理论)

    GIL全局解释器锁,本质上就是一把互斥锁,保证数据安全

    在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

    GIL全局解释器锁的优缺点

    优点:

    保证数据的安全

    缺点:

    单个进程下,开启多个线程,牺牲执行效率,无法实现并行

    注意:

    IO密集型下使用多线程

    计算密集型下使用多进程

    协程

    进程:资源单位

    线程:执行单位

    协程:单线程下实现并发

    ​ 在IO密集型情况下,使用协程能将效率提到最高

    PS:协程不是任何单位,只是程序员YY出来的东西

    PS:将效率提到极致的方法,多进程--->多线程--->让每个线程都实现协程

    协程的目的:

    手动实现“遇到IO切换 + 保存状态”去欺骗操作系统,让操作系统误以为没有IO操作,将CPU的执行权限给你

    from gevent import monkey  # 猴子补丁
    monkey.patch_all()  # 监听所有的任务是否有IO操作
    from gevent import spawn  # spawn(任务)
    from gevent import joinall
    import time
    
    
    def task1():
        print('start from task1...')
        time.sleep(1)
        print('end from task1...')
    
    
    def task2():
        print('start from task2...')
        time.sleep(3)
        print('end from task2...')
    
    
    def task3():
        print('start from task3...')
        time.sleep(5)
        print('end from task3...')
    
    
    if __name__ == '__main__':
    
        start_time = time.time()
        sp1 = spawn(task1)
        sp2 = spawn(task2)
        sp3 = spawn(task3)
    
        joinall([sp1, sp2, sp3])
    
        end_time = time.time()
    
        print(f'消耗时间: {end_time - start_time}')
    
    # 打印结果:
    # start from task1...
    # start from task2...
    # start from task3...
    # end from task1...
    # end from task2...
    # end from task3...
    # 消耗时间: 5.02597451210022
    
  • 相关阅读:
    how to pass a Javabean to server In Model2 architecture.
    What is the Web Appliation Archive, abbreviation is "WAR"
    Understaning Javascript OO
    Genetic Fraud
    poj 3211 Washing Clothes
    poj 2385 Apple Catching
    Magic Star
    关于memset的用法几点
    c++ 函数
    zoj 2972 Hurdles of 110m
  • 原文地址:https://www.cnblogs.com/aheng/p/12012930.html
Copyright © 2011-2022 走看看