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
    
  • 相关阅读:
    Ubuntu解压缩命令
    小语种优化策略
    外贸seo常用Zen Cart数据库mysql批量执行命令
    ASP Request.ServerVariables
    centos linux 下 crontab e 命令插入及保存
    AJAX 传值给后台
    partial class
    Jquery 实现弹出层
    abstract class
    SQL锁表
  • 原文地址:https://www.cnblogs.com/aheng/p/12012930.html
Copyright © 2011-2022 走看看