• 进程的介绍
    在Python程序中,想要实现多任务可以使用进程来完成,进程是实现多任务的一种方式。
  • 进程的概念
    一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
    比如:现实生活中的公司可以理解成是一个进程,公司提供办公资源(电脑、办公桌椅等),真正干活的是员工,员工可以理解成线程。
  • 注意
  • python多任务之多进程_子进程

  • 一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程
  • python实现多进程

第一种方法通过对象名= multiprocessing.Process()生成对象

# 1. 导入multiprocessing包
import multiprocessing
import time


# 定义跳舞的任务函数
def dance():
for i in range(3):
print("跳舞中...")
time.sleep(0.2)


# 定义唱歌的任务函数
def sing():
for i in range(3):
print("唱歌中...")
time.sleep(0.2)

# 判断是否是主模块
if __name__ == '__main__':
# dance()
# sing()

# 2. 创建进程对象
# group : 进程组,目前只能使用None, 一般不要管它
# target: 指定执行的任务名
sub_process1 = multiprocessing.Process(target=dance)
sub_process2 = multiprocessing.Process(target=sing)


# 3. 启动进程执行对应的任务
sub_process1.start()
sub_process2.start()

第二种,继承Process类,重写类的方法run()

from multiprocessing import Process
import os
import time
class MyProcess(Process):
#重新init方法
def __init__(self,interval):
#下面一句是调用父类init方法,这一本尽量不要少,因为父类还有很多事情需要在init方法内处理
Process.__init__(self)
self.interval=interval

#重写run方法
def run(self):
'''
该方法中可书写为需要的执行的进程代码
'''
print("子进程运行中,pid=%d,父进程:%d" % (os.getpid(), os.getppid()))
t_start=time.time()
time.sleep(self.interval)
t_end=time.time()
print("子进程运行结束,耗时:%0.2f秒"%(t_end-t_start))

if __name__=="__main__":
t_start=time.time()
print("父进程开始执行")
p=MyProcess(2)
p.start()
p.join()
t_end=time.time()
print("父进程运行结束,耗时:%0.2f秒" % (t_end - t_start))
结果:

父进程开始执行
子进程运行中,pid=20728,父进程:20727
子进程运行结束,耗时:2.00秒
父进程运行结束,耗时:2.02秒
  • 获取当前进程以及进程的编号
# 1. 导入multiprocessing包
import multiprocessing
import time
import os

# 定义跳舞的任务函数
def dance():
# 扩展: 获取当前代码的进程对象
dance_process = multiprocessing.current_process()
print("dance_process:", dance_process)
# 获取当前进程(子进程)编号
dance_pid = os.getpid()
# 获取当前进程的父进程编号
p_pid = os.getppid()
print("dance_process的父进程编号:", p_pid)
print("dance_processid:", dance_pid)
for i in range(3):
print("跳舞中...")
time.sleep(0.2)


# 定义唱歌的任务函数
def sing():
# 扩展: 获取当前代码的进程对象
sing_process = multiprocessing.current_process()
print("sing_process:", sing_process)
# 获取当前进程(子进程)编号
sing_pid = os.getpid()
# 获取当前进程的父进程编号
p_pid = os.getppid()
print("sing_process的父进程编号:", p_pid)
print("sing_processid:", sing_pid)
for i in range(3):
print("唱歌中...")
time.sleep(0.2)

# 判断是否是主模块
if __name__ == '__main__':

# 扩展: 获取当前代码的进程对象
main_process = multiprocessing.current_process()
print("main:", main_process)
# 获取当前进程(主进程)的编号
main_pid = os.getpid()
print("main_pid:", main_pid)

# 2. 创建进程对象
# group : 进程组,目前只能使用None, 一般不要管它
# target: 指定执行的任务名
# name: 进程名,如果不指定默认的命名格式: Process-1, ....
sub_process1 = multiprocessing.Process(target=dance, name="sub_process1")
sub_process2 = multiprocessing.Process(target=sing, name="sub_process2")
print("sub_process1:", sub_process1)
print("sub_process2:", sub_process2)

# 3. 启动进程执行对应的任务
sub_process1.start()
sub_process2.start()
  • 多进程任务的传参
import multiprocessing


def task(count, name):
print(name)
print(multiprocessing.current_process())
for i in range(count):
print("%d任务执行中..." % i)


if __name__ == '__main__':

# 创建子进程
# 元组如果只有一个元素,那么元素后面的逗号不能省略
# 两种传参方式
# ①args: 表示以元组方式给执行任务传参数, 实际上是按照函数位置参数进行传参的。
sub_process = multiprocessing.Process(target=task, args=(3, "张三"), name="sub_process")
sub_process.start()

# ②kwargs: 表示以字典方式给执行任务传参数, 实际上是按照函数关键字传参的。
# sub_process = multiprocessing.Process(target=task, kwargs={"name":"zs", "count":1})
# sub_process.start()
  • 进程之间不共享全局变量
import multiprocessing
import time

# 测试代码
# result = type([])
# print(result)

# 定义全局变量
g_list = list() # => [] 表示一个空列表


# 向全局变量添加数据
def add_data():
for i in range(3):
g_list.append(i)
print("add_data:", i)
time.sleep(0.2)

print("数据添加完毕:", g_list)


# 读取全局变量的数据
def read_data():
print("read_data:", g_list)

if __name__ == '__main__':
# 添加数据进程
add_data_process = multiprocessing.Process(target=add_data)
# 读取数据进程
read_data_process = multiprocessing.Process(target=read_data)

# 启动进程执行对应的任务
add_data_process.start()
# 进程等待 join, 主进程会等待子进程(add_data_process)执行完成以后,再继续执行下面的代码
add_data_process.join()
read_data_process.start()

print("获取主进程里面的g_list", g_list)
  • 主进程会等待子进程执行完毕再结束,如何让子进程跟随主进程相生相悼
import multiprocessing
import time


def task():
for i in range(3):
print("正在工作中...")
time.sleep(0.2)


if __name__ == '__main__':
# 创建子进程
sub_process = multiprocessing.Process(target=task)
# 设置守护主进程,主进程退出子进程就销毁,停止运行
sub_process.daemon = True

# 启动子进程执行对应的任务
sub_process.start()

# 主进程延时0.3秒
time.sleep(0.3)
# 让子进程销毁
# sub_process.terminate()
print("主进程over")
# exit()

# 默认:主进程会等待所有的子进程执行完成以后主进程再退出

# 主进程不等待子进程的操作
# 1. 主进程在退出执行,先让子进程进行销毁 sub_process.terminate()
# 2. 设置守护主进程,子进程名.daemon=True,主进程退出子进程直接销毁,不再执行子进程里面的任务
  • 既然进程之间不能共享全局变量,如何让进程之间能够完成某种资源的通信, 可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息列队程序,首先用一个小实例来演示一下Queue的工作原理:
import multiprocessing
import time

if __name__ == '__main__':
# 创建消息队列, 3:表示队列中最大消息个数
queue = multiprocessing.Queue(3)
# 放入数据
queue.put(1)
queue.put("hello")
queue.put([3,5])
# 总结: 队列可以放入任意数据类型
# 提示: 如果队列满了,需要等待队列有空闲位置才能放入数据,否则一直等待
# queue.put((5,6))
# 提示: 如果队列满了,不等待队列有空闲位置,如果放入不成功直接崩溃
# queue.put_nowait((5,6))
# 建议: 向队列放入数据统一使用put

# 查看队列是否满了
# print(queue.full())

# 注意点:queue.empty()判断队列是否空了不可靠
# 查看队列是否空了
# print(queue.empty())

# 解决办法: 1. 加延时操作 2. 使用判断队列的个数,不使用empty
# time.sleep(0.01)
if queue.qsize() == 0:
print("队列为空")
else:
print("队列不为空")

# 获取队列的个数
size = queue.qsize()
print(size)

# 获取数据
value = queue.get()
print(value)
# 获取队列的个数
size = queue.qsize()
print(size)
# 获取数据
value = queue.get()
print(value)
# 获取数据
value = queue.get()
print(value)

# 获取队列的个数
size = queue.qsize()
print(size)

# 提示:如果队列空了,再取值需要等待,只有队列有值以后才能获取队列中数据
# value = queue.get()
# print(value)
# 提示: 如果队列空了 ,不需要等待队列有值,但是如果取值的时候发现队列空了直接崩溃
# 建议大家: 向队列取值使用get
# value = queue.get_nowait()
# print(value)

运行结果:

队列不为空
3
1
2
hello
[3, 5]
0
初始化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);其中block = 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);
  • 进程之间通信的实例
import multiprocessing
import time

"""主进程往队列中放入屏幕上输入的数据  子进程从队列中取出数据并且打印出来"""


def proc_func(queue):
while True:
if queue.empty():
print("队列是空的 我稍后来取一次")
time.sleep(2)
else:
data = queue.get()
print("从队列中取出数据%s" % data)
time.sleep(2)

if __name__ == '__main__':
# 1 创建一个队列 参数表示队列的最大长度
queue = multiprocessing.Queue(3)

# 2 创建一个子进程
pro = multiprocessing.Process(target=proc_func, args=(queue,))
pro.start()

# 3 录入数据 放入队列
while True:
if queue.full():
print("队列已满")
time.sleep(1)
else:
data = input("请输入:")
queue.put(data)

print("当前队列数据条数是%s" % queue.qsize())
"""如何创建队列 放数据 取数据"""
import multiprocessing

# 新建一个队列对象
queue = multiprocessing.Queue(3)

def send_data(queue,n):
'''发送数据'''
for i in range(n):
queue.put(i)
if queue.full():
print("停止发送退出")
break
print("发送完毕")

def recv_data(queue):
'''收取数据'''
if not queue.empty():
print("正在获取%d",queue.get())


if __name__ == '__main__':
task1 = multiprocessing.Process(target=(queue,4))
task2 = multiprocessing.Process(target=(queue))
task1.start()
# 等待task1进程完成
task1.join()
task2.start()
  • 大量多进程任务的实现 -------进程池Pool
池子里面放的是进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。

初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务.
  • 进程池同步执行任务表示进程池中的进程在执行任务的时候一个执行完成另外一个才能执行,如果没有执行完会等待上一个进程执行
import multiprocessing
import time


# 拷贝任务
def work():
print("复制中...", multiprocessing.current_process().pid)
time.sleep(0.5)

if __name__ == '__main__':
# 创建进程池
# 3:进程池中进程的最大个数
pool = multiprocessing.Pool(3)
# 模拟大批量的任务,让进程池去执行
for i in range(5):
# 循环让进程池执行对应的work任务
# 同步执行任务,一个任务执行完成以后另外一个任务才能执行
pool.apply(work)
  • 进程池异步执行任务:表示进程池中的进程同时执行任务,进程之间不会等待
```python
# 进程池:池子里面放的进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务
import multiprocessing
import time


# 拷贝任务
def work():
print("复制中...", multiprocessing.current_process().pid)
# 获取当前进程的守护状态
# 提示:使用进程池创建的进程是守护主进程的状态,默认自己通过Process创建的进程是不是守住主进程的状态
# print(multiprocessing.current_process().daemon)
time.sleep(0.5)

if __name__ == '__main__':
# 创建进程池
# 3:进程池中进程的最大个数
pool = multiprocessing.Pool(3)
# 模拟大批量的任务,让进程池去执行
for i in range(5):
# 循环让进程池执行对应的work任务
# 同步执行任务,一个任务执行完成以后另外一个任务才能执行
# pool.apply(work)
# 异步执行,任务执行不会等待,多个任务一起执行
pool.apply_async(work)

# 关闭进程池,意思告诉主进程以后不会有新的任务添加进来
pool.close()
# 主进程等待进程池执行完成以后程序再退出
pool.join()
```
  • 备注
multiprocessing.Pool常用函数解析:

apply(func[, args[, kwds]]): 阻塞方式调用函数,args表示以元组方式给函数传参,kwds表示以字典方式给函数传参
apply_async(func[, args[, kwds]]) :使用非阻塞方式调用函数,args表示以元组方式给函数传参,kwds表示以字典方式给函数传参
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;