【Python高级编程】进程管理

26 阅读7分钟

课堂引入

python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。不过,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

授课进程

1、进程简介

日常生活中我们使用能够的每个程序比如qq、微信等这些程序没有启动之前就是一些代码的集合,是静态的,当他们运行起来之后,这些静态的代码加上用到的资源比如内存、摄像头等这个整体称为进程,它是系统分配资源的基本单元,进程是程序的基本执行实体。

2、进程的定义

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元,每一个运行着的程序至少有一个进程。

为了更好的理解进程,举个例子:

我们启动QQ就是启动了一个进程, 这个进程包括了qq本身的代码以及qq所使用的资源(内存、显卡、摄像头、麦克风等),如果我们同时运行两个qq那么此时就会有两个进程。

3、进程的状态

程序执行的一个理想状态是一个cpu核心开启一个进程执行一个程序,但是工作中非常不现实,及其浪费资源,我们的任务数量远远大于cpu核心数,也就数说一个核心会同时接受多个任务,而一个cpu核心同时只能执行一个任务,因此一定有一些任务正在执行,而另一些任务在等待cpu执行, 因此进程有同的状态。

就绪状态: 运行的条件都已近准备好,正在等待cpu执行

执行状态: cpu正在执行中

等待状态: 等待某些条件满足,比如某个地方阻塞了1秒,此时会进入等待状态

1.png

4、进程的创建-multiprocessing

(1) Multiprocessing

先要导入multiprocessing模块: import multiprocessing

函数式:调用 multiprocessing 模块中的Process()函数来产生新进程。

语法如下:Process([group [, target [, name [, args [, kwargs]]]]])

参数说明:

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

下面是创建多进程的一个实例。

import time
import multiprocessing
def test1():
    while True:
        print("函数1执行")
        time.sleep(1)
def test2():
    while True:
        print("函数2执行")
        time.sleep(1)
if __name__ == '__main__':
    p1 = multiprocessing.Process(target=test2)  # 创建线程p1
    p2 = multiprocessing.Process(target=test1)  # 创建线程p2
    p1.start()  #启动线程
    p2.start()
    print(p1.name) # 输出进程名字
    print(p2.name)
    print("主进程执行完毕")

运行结果如下:

F:\python_lj\python.exe F:/woniu_python/hm_python/多任务/进程/进程.py
Process-1
Process-2
主进程执行完毕
函数2执行
函数1执行
函数2执行
函数1执行
函数2执行
函数1执行
函数2执行

说明:

上面例子中先导入了multiprocession模块,然后创建了test1和test2两个函数, 在main函数中创建了两个进程,进程的参数target指向需要执行的代码(注意指向函数是不需要再后面加括号,加括号表示调用函数,不加括号表示执行函数,他们有本质的区别),start()方法启动进程。两个函数可以同时执行就像一个人同时做两件事情,吃饭和睡觉。

5、进程之间通信-消息队列(Queue)

由于进程之间不能共享全局变量,那么进程之间通信就得使用其他的方式, 操作系统提供了很多机制来实现进程之间的通信使用multiprocessing模块中的Queue实现多进程之间的数据传递。

from multiprocessing import Queue
q=Queue(3) #初始化一个Queue对象,最多可接收三条3消息,如果不传参表示队列长度不定
q.put("第一个消息")
q.put("第二个消息")
print(q.full())  #判断队列是否已满
# 注意: 如果消息队列已经满了会在这里一直等着,可以使用put_nowait():不会等直接报错,只要捕获异常不处理就好
q.put("第三个消息")
print(q.empty()) #判断队列是否为空
if not q.full():
    q.put("第四个消息")
#读取消息时,先判断消息列队是否为空,再读取
#q.get() : 如果队列为空会一直等着,直到有数据为止
if not q.empty():
    for i in range(q.qsize()):
        print(q.get())

运行结果

False
False
第一个消息
第二个消息
第三个消息

说明:

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

Queue.qsize():返回当前队列包含的消息数量;

Queue.empty():如果队列为空,返回True,反之False ;

Queue.full():如果队列满了,返回True,反之False;

Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True。

  • 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出”Queue.Empty”异常。

  • 如果block值为False,消息列队如果为空,则会立刻抛出”Queue.Empty”异常;

  • Queue.get_nowait():相当Queue.get(False)。

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

    • 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出”Queue.Full”异常;
    • 如果block值为False,消息列队如果没有空间可写入,则会立刻抛出”Queue.Full”异常;
  • Queue.put_nowait(item):相当Queue.put(item, False)。

6、使用队列完成多进程之间数据共享

案例4:创建一个队列,用进程1向队列中添加值,然后用进程2去读取队列中的值,实现进程的信息的交互,代码如下:

from multiprocessing import Process, Queue
import os, time
# 向队列中添加数据:
def write(q):
    li = ['aa', 'bb', 'cc']
    for value in li:
        print('存入的数据:' ,value)
        q.put(value) # 向队列中添加数据
        time.sleep(0.5)
# 读数据进程执行的代码:
def read(q):
    while True:
        if not q.empty():
            value = q.get()
            print('读取到的数据:',value)
            time.sleep(0.5)
        else:
            break
if __name__=='__main__':
    q = Queue() # 创建一个队列
    pw = Process(target=write, args=(q,)) # 创建一个进程将队列的引用传过去
    pr = Process(target=read, args=(q,))
    pw.start()
    pw.join()
    pr.start()
    pr.join()
    print('数据读写完毕')

运行结果:

存入的数据: aa
存入的数据: bb
存入的数据: cc
读取到的数据: aa
读取到的数据: bb
读取到的数据: cc
数据读写完毕

课程小结

1、每个进程是系统内独立分配资源的基本单位。

2、进程间可以通过队列实现数据共享。