• python_多进程概念及简单用法、队列、守护进程、进程池


    一。了解 单任务与多任务、串行与并行、并发、进程的概念:

    1)单任务与多任务: 

    单任务:单任务的应用程序(cmd.exe),没办法同时执行多条命令
    多任务:多任务的应用 windowns操作系统,Pycharm,迅雷(同时下载多个文件,边下边播)
    多任务的实现方式:多进程、多线程、协程

      

    2)串行与并行

    串行运行:比如在linux中串行执行多条命令用&,举栗:apt install python & apt install mysql :先下载python然后下载mysql
    并行运行:比如在linux中串行执行多条命令用&&,举栗:apt install python && apt install mysql :同时下载python和mysql
    并行:3台电脑分别安装一个mysql8.0,然后组成mysql集群,3个mysql是同时运行(并行)
    举栗:5个收银员 同时 操作 5 台收款机 --并行

    3)并发

    并发:比如 迅雷同时下载5个电影(单核cpu的话,5个任务反复切换下载)
    举栗:1个收银员,"同时"操作5台 收款机 --并发

    4)进程

    进程:静态的:(软件、程序、代码、函数...) 动态:(软件运行、程序运行、代码运行、函数运行),进程是动态的
    程序是怎么运行的? -->操作系统 创建一个进程(加载代码到内存,为变量分配内存,建个档案:process id PID)

    二。进程介绍,举栗

    实现需求:

    使用多进程实现并发下载图片
    -多进程会独享资源,2个进程不能共享变量(例如:不能共享list),如何实现共享资源那?(队列Queue,内存数据库redis...)
    -多线程占用的资源最少,2个线程是共享变量的,迅雷就是使用多线程下载技术的。

    运行代码:

      

    #引入进程模块
    from multiprocessing import Process, Queue
    import requests
    import os
    #看不懂没关系后面会详细介绍
    def download(q: Queue):
    while True:
    url = q.get()
    r = requests.get(url)
    img_name = url.split('/')[-1]
    img_name = os.path.join('./', img_name)
    with open(img_name, 'wb') as f: #把服务器返回的信息(图片)保存到文件中
    f.write(r.content)
    if q.empty():
    break


    if __name__ == '__main__':
    tasks = Queue()

    #put相当于list中的append方法
    tasks.put("https://dss0.bdstatic.com/-0U0bnSm1A5BphGlnYG/tam-ogel/067d0e8627b667778ea8729e6e480a47_254_96.png")
    tasks.put("https://dss0.bdstatic.com/-0U0bnSm1A5BphGlnYG/tam-ogel/3f52ff39e52f182d1bfd9ce061e9c6a6_254_144.jpg")

    # 创建两个来执行download函数的进程p1,p2
    p1 = Process(target=download, args=(tasks, ))
    p2 = Process(target=download, args=(tasks, ))

    #启动进程p1,p2
    p1.start()
    p2.start()
    p1.join() #加上阻塞
    p2.join() #加上阻塞,等p1,p2运行结束后关闭主进程,不然主进程会直接运行结束,那么子进程尚未运行结束就会找不到目录,因为主进程内存已经销毁了
    #进程启动后,是由操作系统控制执行
    print("主进程运行结束")

    三。进程中的队列:Queue

    1)使用list迭代对象存放需要处理的数据,list会存储在储存区,开多进程的话都会被执行,举栗:

    from multiprocessing import Process, Queue
    import time

    # 1.进程,list,未使用Queue
    def work1(q):
    """

    :param q: 要处理的所有任务
    :return:
    """
    print("这个是work2中的参数q的id: ", id(q))
    while q:
    print(f"work1 从q中获得了 {q.pop()}")


    def work2(q):
    """

    :param q: 要处理的所有任务
    :return:
    """
    print("这个是work2中的参数q的id: ", id(q))
    while q:
    print(f"work2 从q中获得了 {q.pop()}")


    if __name__ == '__main__':
    # 进程1,使用list,work1与work2会做重复工作,因为会把list分别存在储存区,子进程p1与p2独享list
    q = ['a', 'b', 'c']
    p1 = Process(target=work1, args=(q,)) #子进程q1,单独把q存储在内存区不与p2子进程共享q(两个q的id不同)
    p2 = Process(target=work2, args=(q,)) #子进程q2,单独把q存储在内存区不与p1子进程共享q(两个q的id不同)
    p1.start()
    p2.start()
    print(f'主进程运行完毕!')

    运行结果,开启了两个进程p1与p2,他们分别把执行任务q都执行了一遍:

    2)使用队列:Queue,他通过put方法添加执行任务到队列,分别执行队列中的执行任务

    from multiprocessing import Process, Queue


    # 1.进程,使用Queue共享变量
    def work3(q: Queue):
    """

    :param q: 要处理的所有任务
    :return:
    """
    time.sleep(0.5)
    print("这个是work3中的参数q的id: ", id(q))
    while not q.empty():
    print(f"work3 从q中获得了 {q.get()}")


    def work4(q: Queue):
    """

    :param q: 要处理的所有任务
    :return:
    """
    time.sleep(0.5)
    print("这个是work4中的参数q的id: ", id(q))
    while not q.empty():
    print(f"work4 从q中获得了 {q.get()}")


    if __name__ == '__main__':

    # 进程2,使用Queue(队列):work3与work4共同完成q中的所有任务 Queue 是被p1、p2共享的
    q = Queue()
    q.put('a')
    q.put('b')
    q.put('c')
    p1 = Process(target=work3, args=(q,))
    p2 = Process(target=work4, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print(f'主进程运行完毕!')

    运行结果,开启了两个进程p1与p2,可以明显看出他们分别执行了不同的任务:

    四。多进程类的使用:继承Process,调用时会自动执行run()方法

    from multiprocessing import Process


    class MyProcess(Process):
    def __init__(self):
    super().__init__()

    def run(self):
    print("hello")

    if __name__ == '__main__':
    p1 = MyProcess()
    p2 = MyProcess()

    p1.start() #会调用MyProcess中的run方法
    p2.start()
    运行结果,可以看出,他把run()方法的内容执行了两次,暂时还没有想到比较好的使用方法(可以通过封装不同方法,最后在run方法中调用并执行),感觉使用之前队列的已经可以让执行效率提高很多了:

    五。多进程与单一进程对比,守护进程:守护进程(Process().daemon ,默认值是False,)

     1)单进程与多进程对比

    import os
    import time

    from multiprocessing import Process

    def play_music():
    print("子进程p1,PID:", os.getpid())
    for i in range(5):
    print('play music ...')
    time.sleep(1)


    def play_lol():
    print("子进程p2,PID:", os.getpid())
    for i in range(5):
    print('play lol ...')
    time.sleep(1)

    if __name__ == '__main__':
    #1.第一种方式:运行了10秒
    start1 = time.time()
    play_music()
    play_lol()
    end1 = time.time()
    print(f"单进程使用时间是:{end1 - start1:.2f}")

    运行结果,可以明显看出单进行执行完成需要10秒左右:

    2)多进程

    import os
    import time

    from multiprocessing import Process

    def play_music():
    print("子进程p1,PID:", os.getpid())
    for i in range(5):
    print('play music ...')
    time.sleep(1)


    def play_lol():
    print("子进程p2,PID:", os.getpid())
    for i in range(5):
    print('play lol ...')
    time.sleep(1)

    if __name__ == '__main__':

    #1.第二种方式:使用多进程运行,运行了5秒,说明了在同时运行
    start_time = time.time()
    p1 = Process(target=play_music)
    p2 = Process(target=play_lol)

    p1.start() #操作系统 启动一个进程 用来运行 target=play_music 的进程,这个进程是子进程(不是完整的代码)
    p2.start()

    p1.join()
    p2.join() #代表 运行完p1与p2的子进程后 在运行主进


    end_time = time.time()
    # 统计子进程所用时间
    print(f"多进程所用的时间{end_time-start_time}")
    print("主进程pid: ", os.getpid())

    运行结果,开启了两个进程 可以明显看出 执行时间是5秒左右:

    3)守护进程:Process().daemon,在主进程运行结束后,子进程依然运行直到子进程全部运行结束

    import os
    import time

    from multiprocessing import Process

    def play_music():
    print("子进程p1,PID:", os.getpid())
    for i in range(5):
    print('play music ...')
    time.sleep(1)


    def play_lol():
    print("子进程p2,PID:", os.getpid())
    for i in range(5):
    print('play lol ...')
    time.sleep(1)

    if __name__ == '__main__':

    #1.第二种方式:使用多进程运行,运行了5秒,说明了在同时运行
    start_time = time.time()
    p1 = Process(target=play_music)
    p2 = Process(target=play_lol)

    #6)守护进程,使用在进程启动之前。默认值是False,当为True的时候,主进程结束,会强制结束子进程
    p1.daemon = False
    p2.daemon = False

    #2)查看进程使用信息
    #print(help(p2))
    p1.start() #操作系统 启动一个进程 用来运行 target=play_music 的进程,这个进程是子进程(不是完整的代码)
    p2.start()


    end_time = time.time()
    # 5)统计子进程所用时间
    print(f"多进程所用的时间{end_time-start_time}")
    print("主进程pid: ", os.getpid())

    运行结果,可以明显看出,主进程已经运行结束了,子进程还在运行:

    六。进程池:Pool,在介绍进程池前先介绍两个经典的进程池方法:map、apply

    1)map与map_async

    map:

    #map
    # 调用python3的进程池
    """
    map:把一个可迭代对象 映射到 函数(有几个迭代参数运行几次),同步执行
    """
    from multiprocessing import Pool
    import time


    def square(x):
    result = x * x
    time.sleep(1)
    return result


    if __name__ == '__main__':
    start = time.time()
    #创建9个进程
    pool = Pool(9)
    result = pool.map(square, [1, 2, 3, 4]) #map会阻塞主程序(使用了map就不需要使用join(),作用:一系列的数据应用到函数身上,并使用多进程)
    print(result)
    pool.close()
    endtimes1 = time.time()
    #打印所用时间并保存两位小数,:.2f
    print(f"总共使用了{(endtimes1 - start):.2f}")

    运行结果:

    map_async:

    """
    什么时候用map什么时候用map_async:当不需要打印结果的时候使用map_async,因为map 也是调用 map_async只是多了一步".get"的方法,所以直接运行map_async比较快
    map_async:把一个可迭代对象 映射到 函数(有几个迭代参数运行几次),异步执行
    """

    from multiprocessing import Pool
    import time

    def square(x):
    time.sleep(2)
    result = x * x
    print("~~~~~~~~~~")
    return result


    if __name__ == '__main__':
    start = time.time()
    pool = Pool(8)
    result = pool.map_async(square, [1, 2, 3, 4]) #不会阻塞主程序
    #print(result.get()) #.get() :是获取运行结果,每运行完一次就会拿一次结果然后再执行下一次,实质上就是阻塞 等同于pool.map 阻塞方法
    end = time.time()
    print(f"总共使用了{(end - start):.2f}")
    print("主程序结束")

    运行结果:

    map与map_async对比:

    map其实调用的是map_async方法,只是比map_async多了一步".get()",如下图源码:

    2)apply与apply_async区别:


    """
    apply:调用函数,传递任意参数(运行一次)

    """
    import time
    from multiprocessing import Pool


    def test(x, y, z=0):
    print("子程序正在运行")
    #pass


    if __name__ == '__main__':
    pool = Pool(2)
    #apply(self, func, args=(), kwds={})
    pool.apply(test, (2, 3), {"z": 1}) #阻塞 主程序(也就是主进程),apply传的参数是元组类型的任意参数,map是可迭代对象
    pool.apply_async(test, (2, 3), {"z": 1}) # 不阻塞 主程序(也就是主进程)
    print("主程序运行结束")
    print(help(pool.apply))

    运行结果,可以看到无论传递多少参数其实只运行了一次:

    apply与apply_async区别,apply其实调用的是apply_async方法只是多了".get()"方法,如下图源码:

    3)map与apply的区别:

    apply:调用函数,传递任意参数(运行一次)
    map:把一个可迭代对象 映射到 函数(有几个迭代参数运行几次)
    _async:如果加这个名字的进程函数,都是不阻塞的函数方法
    同步(阻塞)与异步(不阻塞)区别:
    同步 :该方法不运行完成 主程序不会结束
    异步:可以执行多个异步方法
    爱折腾的小测试
  • 相关阅读:
    js判断是否为数字
    sublime3常用插件总结
    npm run build打包后自定义动画没有执行
    css实现未知元素宽高垂直居中和水平居中的方法
    vue3.0的设计目标
    chrome 80版本以后开启了SameSite
    http以及缓存、事件循环......
    vue移动端适配就这么玩
    前端性能监控
    能解决 80% 需求的 10个 CSS动画库
  • 原文地址:https://www.cnblogs.com/newsss/p/14617298.html
Copyright © 2020-2023  润新知