• python基础知识整理 08-多任务:进程


    1 进程以及状态

    1.1 进程概念

    一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。

    不仅可以通过线程完成多任务,进程也是可以的

    1.2 进程的状态

    • 就绪态:运行的条件都已经慢去,正在等在cpu执行
    • 执行态:cpu正在执行其功能
    • 等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态

    2 进程的创建

    multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情。

    2.1 两个while循环一起执行

    创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动

    1. # -*- coding: utf-8 -*-
    2. from multiprocessing import Process
    3. import time
    4. def run_proc():
    5. """子进程要执行的代码"""
    6. while True:
    7. print("----2----")
    8. time.sleep(1)
    9. if __name__ == '__main__':
    10. p = Process(target=run_proc)
    11. p.start()
    12. while True:
    13. print("----1----")
    14. time.sleep(1)

    2.2 进程pid

    1. # -*- coding: utf-8 -*-
    2. from multiprocessing import Process
    3. import os
    4. def run_proc():
    5. """子进程要执行的代码"""
    6. print('子进程运行中,pid=%d...' % os.getpid()) # os.getpid获取当前进程的进程号
    7. print('子进程将要结束...')
    8. if __name__ == '__main__':
    9. print('父进程pid: %d' % os.getpid()) # os.getpid获取当前进程的进程号
    10. p = Process(target=run_proc)
    11. p.start()

    2.3 Process语法结构

    Process([group [, target [, name [, args [, kwargs]]]]])

    • target:如果传递了函数的引用,可以任务这个子进程就执行这里的代码
    • args:给target指定的函数传递的参数,以元组的方式传递
    • kwargs:给target指定的函数传递命名参数
    • name:给进程设定一个名字,可以不设定
    • group:指定进程组,大多数情况下用不到

    Process创建的实例对象的常用方法:

    • start():启动子进程实例(创建子进程)
    • is_alive():判断进程子进程是否还在活着
    • join([timeout]):是否等待子进程执行结束,或等待多少秒
    • terminate():不管任务是否完成,立即终止子进程

    Process创建的实例对象的常用属性:

    • name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
    • pid:当前进程的pid(进程号)

    2.4 给子进程指定的函数传递参数

    1. # -*- coding: utf-8 -*-
    2. from multiprocessing import Process
    3. import os
    4. from time import sleep
    5. def run_proc(name, age, **kwargs):
    6. for i in range(10):
    7. print('子进程运行中,name= %s,age=%d ,pid=%d...' % (name, age, os.getpid()))
    8. print(kwargs)
    9. sleep(0.2)
    10. if __name__ == '__main__':
    11. p = Process(target=run_proc, args=('test', 18), kwargs={"m": 20})
    12. p.start()
    13. sleep(1) # 1秒中之后,立即结束子进程
    14. p.terminate()
    15. p.join()
    16. """
    17. 子进程运行中,name= test,age=18 ,pid=142080...
    18. {'m': 20}
    19. 子进程运行中,name= test,age=18 ,pid=142080...
    20. {'m': 20}
    21. 子进程运行中,name= test,age=18 ,pid=142080...
    22. {'m': 20}
    23. 子进程运行中,name= test,age=18 ,pid=142080...
    24. {'m': 20}
    25. """

    2.5 不同进程共享全局变量

    1. # -*- coding: utf-8 -*-
    2. from multiprocessing import Process
    3. import os
    4. import time
    5. nums = [11, 22]
    6. def work1():
    7. """子进程要执行的代码"""
    8. print("in process1 pid=%d ,nums=%s" % (os.getpid(), nums))
    9. for i in range(3):
    10. nums.append(i)
    11. time.sleep(1)
    12. print("in process1 pid=%d ,nums=%s" % (os.getpid(), nums))
    13. def work2():
    14. """子进程要执行的代码"""
    15. print("in process2 pid=%d ,nums=%s" % (os.getpid(), nums))
    16. if __name__ == '__main__':
    17. p1 = Process(target=work1)
    18. p1.start()
    19. p1.join()
    20. p2 = Process(target=work2)
    21. p2.start()
    22. """
    23. in process1 pid=137576 ,nums=[11, 22]
    24. in process1 pid=137576 ,nums=[11, 22, 0]
    25. in process1 pid=137576 ,nums=[11, 22, 0, 1]
    26. in process1 pid=137576 ,nums=[11, 22, 0, 1, 2]
    27. in process2 pid=140280 ,nums=[11, 22]
    28. """

    3 进程线程对比

    3.1 功能

    • 进程,能够完成多任务,比如 在一台电脑上能够同时运行多个QQ
    • 线程,能够完成多任务,比如 一个QQ中的多个聊天窗口

    3.2 定义不同

    • 进程是系统进行资源分配和调度的一个独立单位.
    • 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

    3.3 区别

    • 一个程序至少有一个进程,一个进程至少有一个线程.
    • 线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
    • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
    • 线程不能够独立执行,必须依存在进程中
    • 可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人

    3.4 优缺点

    线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。

    4 进程间通信-Queue

    4.1 Queue的使用

    Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信。

    可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息列队程序。

    初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);

    • Queue.qsize():返回当前队列包含的消息数量;
    • Queue.empty():如果队列为空,返回True,反之False ;
    • Queue.full():如果队列满了,返回True,反之False;
    • Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;
    1. 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;
    2. 如果block值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;

    Queue.get_nowait():相当Queue.get(False);

    Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;

    1. 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常;
    2. 如果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;Queue.put_nowait(item):相当Queue.put(item, False);
    1. # -*- coding: utf-8 -*-
    2. from multiprocessing import Queue
    3. q = Queue(3) # 初始化一个Queue对象,最多可接收三条put消息
    4. q.put("消息1")
    5. q.put("消息2")
    6. print(q.full()) # False
    7. q.put("消息3")
    8. print(q.full()) # True
    9. # 因为消息列队已满下面的try都会抛出异常,第一个try会等待2秒后再抛出异常,第二个Try会立刻抛出异常
    10. try:
    11. q.put("消息4", True, 2)
    12. except:
    13. print("消息列队已满,现有消息数量:%s" % q.qsize())
    14. try:
    15. q.put_nowait("消息4")
    16. except:
    17. print("消息列队已满,现有消息数量:%s" % q.qsize())
    18. # 推荐的方式,先判断消息列队是否已满,再写入
    19. if not q.full():
    20. q.put_nowait("消息4")
    21. # 读取消息时,先判断消息列队是否为空,再读取
    22. if not q.empty():
    23. for i in range(q.qsize()):
    24. print(q.get_nowait())
    25. """
    26. False
    27. True
    28. 消息列队已满,现有消息数量:3
    29. 消息列队已满,现有消息数量:3
    30. 消息1
    31. 消息2
    32. 消息3
    33. """

    4.2 Queue实例

    Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据

    1. # -*- coding: utf-8 -*-
    2. from multiprocessing import Process, Queue
    3. import os, time, random
    4. # 写数据进程执行的代码:
    5. def write(q):
    6. for value in ['A', 'B', 'C']:
    7. print('Put %s to queue...' % value)
    8. q.put(value)
    9. time.sleep(random.random())
    10. # 读数据进程执行的代码:
    11. def read(q):
    12. while True:
    13. if not q.empty():
    14. value = q.get(True)
    15. print('Get %s from queue.' % value)
    16. time.sleep(random.random())
    17. else:
    18. break
    19. if __name__ == '__main__':
    20. # 父进程创建Queue,并传给各个子进程:
    21. q = Queue()
    22. pw = Process(target=write, args=(q,))
    23. pr = Process(target=read, args=(q,))
    24. # 启动子进程pw,写入:
    25. pw.start()
    26. # 等待pw结束:
    27. pw.join()
    28. # 启动子进程pr,读取:
    29. pr.start()
    30. pr.join()
    31. # pr进程里是死循环,无法等待其结束,只能强行终止:
    32. print('')
    33. print('所有数据都写入并且读完')
    34. """
    35. Put A to queue...
    36. Put B to queue...
    37. Put C to queue...
    38. Get A from queue.
    39. Get B from queue.
    40. Get C from queue.
    41. 所有数据都写入并且读完
    42. """

    5 进程池

    5.1 multiprocessing模块的Pool方法

    初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务

    1. # -*- coding: utf-8 -*-
    2. from multiprocessing import Pool
    3. import os, time, random
    4. def worker(msg):
    5. t_start = time.time()
    6. print("%s开始执行,进程号为%d" % (msg, os.getpid()))
    7. # random.random()随机生成0~1之间的浮点数
    8. time.sleep(random.random() * 2)
    9. t_stop = time.time()
    10. print(msg, "执行完毕,耗时%0.2f" % (t_stop - t_start))
    11. if __name__ == '__main__':
    12. po = Pool(3) # 定义一个进程池,最大进程数3
    13. for i in range(0, 10):
    14. # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
    15. # 每次循环将会用空闲出来的子进程去调用目标
    16. po.apply_async(worker, (i,))
    17. print("----start----")
    18. po.close() # 关闭进程池,关闭后po不再接收新的请求
    19. po.join() # 等待po中所有子进程执行完成,必须放在close语句之后
    20. print("-----end-----")
    21. """
    22. ----start----
    23. 0开始执行,进程号为22380
    24. 1开始执行,进程号为22488
    25. 2开始执行,进程号为21156
    26. 2 执行完毕,耗时0.23
    27. 3开始执行,进程号为21156
    28. 1 执行完毕,耗时0.36
    29. 4开始执行,进程号为22488
    30. 0 执行完毕,耗时0.41
    31. 5开始执行,进程号为22380
    32. 5 执行完毕,耗时0.55
    33. 6开始执行,进程号为22380
    34. 3 执行完毕,耗时1.47
    35. 7开始执行,进程号为21156
    36. 4 执行完毕,耗时1.92
    37. 8开始执行,进程号为22488
    38. 7 执行完毕,耗时0.83
    39. 9开始执行,进程号为21156
    40. 6 执行完毕,耗时1.81
    41. 9 执行完毕,耗时0.53
    42. 8 执行完毕,耗时1.63
    43. -----end-----
    44. """

    5.2 multiprocessing.Pool常用函数

    • apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
    • close():关闭Pool,使其不再接受新的任务;
    • terminate():不管任务是否完成,立即终止;
    • join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;

    5.3 进程池中的Queue

    使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否则会报如下的错误:

    RuntimeError: Queue objects should only be shared between processes through inheritance.

    1. # -*- coding: utf-8 -*-
    2. from multiprocessing import Manager, Pool
    3. import os, time, random
    4. def reader(q):
    5. print("reader启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    6. for i in range(q.qsize()):
    7. print("reader从Queue获取到消息:%s" % q.get(True))
    8. def writer(q):
    9. print("writer启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    10. for i in "itcast":
    11. q.put(i)
    12. if __name__ == "__main__":
    13. print("(%s) start" % os.getpid())
    14. q = Manager().Queue() # 使用Manager中的Queue
    15. po = Pool()
    16. po.apply_async(writer, (q,))
    17. time.sleep(1) # 先让上面的任务向Queue存入数据,然后再让下面的任务开始从中取数据
    18. po.apply_async(reader, (q,))
    19. po.close()
    20. po.join()
    21. print("(%s) End" % os.getpid())
    22. """
    23. (10840) start
    24. writer启动(19680),父进程为(10840)
    25. reader启动(20656),父进程为(10840)
    26. reader从Queue获取到消息:i
    27. reader从Queue获取到消息:t
    28. reader从Queue获取到消息:c
    29. reader从Queue获取到消息:a
    30. reader从Queue获取到消息:s
    31. reader从Queue获取到消息:t
    32. (10840) End
    33. """

     

  • 相关阅读:
    【MySQL】索引的增删查
    全屋智能:鱼很大,但水更深
    Python实现给图片加水印功能
    vue3 nextTick
    C#Linq中的GroupBy
    Linux高性能服务器编程——ch1笔记
    CF思维训练,2020长春CCPC(A,D)
    JAVA餐饮掌上设备点餐系统计算机毕业设计Mybatis+系统+数据库+调试部署
    LLM大模型工程师面试经验宝典--基础版(2024.7月最新)
    Discuz论坛帖子标题随机高亮颜色,拒绝千篇一律!
  • 原文地址:https://blog.csdn.net/bnever/article/details/125598993