1、注意:pool必须在 if name == 'main'下面运行,不然会报错
2、多进程内出现错误会直接跳过该进程,并且默认不会打印错误信息
3、if__name__下面的数据需要通过参数传入主函数里面,不然主函数获取不到该数据值而报错。
4、若不通过传参形式传入数据,可以定义全局变量。但是全局变量的值不能在多进程里面进行修改。
import threading
import multiprocessing
import os # 用于获取当前执行的文件名
import time, random
import traceback # 用于捕获异常
import sys # 用于捕获异常
def main_func(i): # 多进程运行的函数
try: # 多进程中发生异常是不会打印错误信息,并且当前进程会直接跳过,所以异常需要自行捕获
rand_time = random.randint(1, 3) # 随机产生1~3的整数
time.sleep(rand_time)
print(i)
except:
current_filename = str(os.path.basename(sys.argv[0]))[:-3] # 获取当前文件名称
cur_err_filname = current_filename + '_error.txt'
error_info = sys.exc_info() # 打印异常
with open(f'{cur_err_filname}', 'a') as f:
error_str = f'{i}:ERROR OCCURRED,{time.strftime("%Y-%m-%d %H:%M:%S")}:\n {error_info[0]}: {error_info[1]}' # 记录当前进程特征值,错误发生时间 ,错误类型,错误概述
print(error_str, file=f) # 通过打印方式写入文件
traceback.print_tb(error_info[2], file=f) # 错误细节描述(包括bug的代码位置)
f.write(f"{'=' * 50}\n") # 分行
if __name__ == '__main__': # 必须在此语句下面运行,不然会报错
## 普通进程
# 三种模式:fork、spawn, forkserver
"""
spawn:父进程启动一个新的Python解释器, 子进程将只继承运行run()方法所需的资源。
不继承父进程不必要的文件描述符和句柄(一种特殊的只能指针)。
与使用fork或forkserver相比,使用此方法启动进程相当慢。
在Unix和Windows上可用,Windows上为默认
不会拷贝主进程内的数据,如果需要使用主进程的值,需要通过传参的方式将数据传入子进程。
fork:父进程使用os.fork()来分叉Python解释器,
子进程开始时,与父进程实际上是相同的。
父进程所有资源都由子进程继承。这不能保证多线程的安全问题。
会拷贝主进程上的数据到子进程中,并且不共享内存。
forkserver:当程序启动并选择forkserver start方法时,将启动服务器进程。
从那时起,每当需要新进程时,父进程连接到服务器,并请求它分配一个新进程。
叉服务器进程是单线程的,因此它可以安全使用os.fork()。
没有不必要的资源被继承。和spawn类似,一些数据需要通过参数传递。
"""
multiprocessing.set_start_method("fork")
p1 = multiprocessing.Process(target=main_func, args=(2,)) # 定义进程
p1.start() # 启动进程
# 查看进程id
os.getpid() # 当前进程id
os.getppid() # 父进程id
# 查看当前进程中的线程数量
len(threading.enumerate())
## 多进程
process_list = []
for i in range(10):
p = multiprocessing.Process(target=main_func, args=(2,))
p.start()
process_list.append(p)
# spawn模式需要加上以下代码:等所有进程循环以后,再join,不然就不是多进程了
# 注:fork模式不用加以下代码
# for item in process_list:
# item.join()
## 进程池使用
"""
一般来说,cpu有多少个,就可以创建多少个进程
进程池比上面的创建多个进程有优势,因为上面创建了多个进程,但实际使用的只有cpu的个数
而进程池是,给定进程数量,就创建那么多进程,等老的进程执行完了以后,再创建新的进程
进程池方法一:
"""
# 查看cpu个数,计算机管理 --> 处理器,有多少行,就有多少个
# 也可以通过以下命令查看
from multiprocessing import Pool # 进程池,用于多进程
multiprocessing.cpu_count()
pool = Pool(5) # 定义进程数量
for i in range(20):
pool.apply_async(main_func, (i,)) # 调用函数执行多进程
pool.close() # 关闭进程池
pool.join() # 阻塞进程,此两部不能少,保证多进程正常运行
"""
进程池方法二:
注:python3有线程池,使用方法和进程池类似
"""
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
def done(res):
data = res.result() # 进程返回的值在此函数中处理
print(data)
pool = ProcessPoolExecutor(4)
for i in range(10):
fur = pool.submit(main_func, (i, ))
fur.add_done_callback(done) # done 的调用由主进程处理(与线程池不同,线程池是由子线程完成这个回调任务,可以通过打印进程号来查看)
pool.shutdown(True) # 让进程卡在此处,等所有进程执行完毕以后,再执行后面的
## 进程之间数据共享问题
"""
1、使用 multiprocessing.Values/Array 的方法来定义和赋值数据,底层基于c语言的,用着不太习惯,了解即可
2、使用列表、字典,将外面的字典传入进程,在进程里面对列表数据进行添加修改等操作,数据是共享的
3、使用 queue = multiprocessing.Queue()队列,在主进程中放入数据,将数据传入子进程,在子进程中是可以获取到父进程的数据的
子进程中 queue.put()放入数据,父进程中queue.get()就可以取到值
反过来,主进程中put,子进程中get也可以
4、基于 parents_data, child_data = multiprocessing.Pipe()。
通过将child_data传入子进程,进程内child_data.recv()就可以获取父进程发送的数据
通过child_data.send(data),就可以将数据发送给父进程,然后parents_data.recv()就可以收到子进程发送的数据
5、基于文件、数据库进行数据共享
"""
## 进程锁:用于解决进程数据共享问题,与多线程类似
# 使用方法与多线程一样,lock通过参数传递进子进程
# lock = multiprocessing.RLock()
# lock.acquire()
# lock.release()
# 进程池的进程锁,需要基于Manager中的Lock和RLock
# 普通进程直接使用上面的Lock即可
# manager = multiprocessing.Manager()
# lock = manager.RLock()