3.3进程
3.3.1进程概述
通俗理解一个运行起来的程序或者软件叫做进程
(1)每次启动一个进程都需要向操作系统索要运行资源(内存),进程是操作系统资源分配的基本单位
(2)进程只提供运行资源,真正干活的是线程,线程是执行程序中对应的代码的, 默认一个进程默认只提供一个线程(主线程),当然还可以在一个进程里面开辟多个线程
(3) 如何理解进程:把公司类比成进程,公司会给我们提供办公资源(办公桌椅,办公电脑等),公司里面的员工利用公司里面的资源去干活,所以公司可以想成进程,员工就是线程
3.3.2进程和线程的对比
(1)进程是操作系统资源分配的基本单位,线程是cpu调度的基本单位,cpu调度哪个线程,哪个线程去执行对应的代码
(2)进程之间不共享全局变量, 线程之间共享全局变量,但是要注意资源竞争的问题,解决问题的方式是互斥锁或者线程同步
(3)多进程开发比单进程多线程开发代码的稳定性要强,但是多进程开发比多线程开发资源开销要多
(4)多进程开发某一个进程挂掉不会影响其它进程的运行,单进程开发该进程挂掉所有的线程都死掉了, 多线程开发会共享进程中资源
(5)先有进程,线程是依附在进程里面的,默认一个进程会提供一个线程(主线程)
3.3.3创建进程
import multiprocessing
sub_process = multiprocessing.Process(group=None, target=show_info, name="myprocess", args=("杨钰莹", 18))
sub_process.start()
参数说明:
(1)group: 进程组, 默认值必须是None
(2)target:执行的目标函数
(3)name: 进程名称, 如果不指定进程名字的格式: Process-1,....
(4)args: 以元组的方式传参
(5)kwargs:以字典的方式传参
3.3.4获取进程、进程编号
import multiprocessing
import os
def work():
# 获取当前进程
current_process = multiprocessing.current_process()
print("work:", current_process)
# 扩展-获取进程编号
print("work的进程编号:", current_process.pid, os.getpid())
# 获取父进程的编号
print("work进程的父进程编号:", os.getppid())
for i in range(10):
print("工作中....")
time.sleep(0.2)
# 扩展: 根据进程编号杀死指定进程
os.kill(os.getpid(), 9)
3.3.5进程注意点
(1)进程之间不共享全局变量
(2)主进程等待子进程完成再退出
若不想主进程一直等待,可用:
①守护主进程
sub_process = multiprocessing.Process(target=test)
sub_process.daemon = True
time.sleep(1)
sub_process.start()
②销毁子进程
sub_process = multiprocessing.Process(target=test)
time.sleep(1)
sub_process.terminate()
sub_process.start()
3.3.6进程间通信——Queue
可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息队列(先进先出)程序
创建消息队列:
queue = multiprocessing.Queue()
说明:
(1)初始化Queue()对象时(例如:queue=Queue()),若括号中没有指
定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数
量没有上限(直到内存的尽头)
(2)放入数据:
queue.put(xxxx)
注意:消息队列可以放入任何数据类型。如果队列满了使用put放入
数据会等待,直到队列有空闲位置才能放入数据。如果队列满了使用
put_nowait不会等待,放入数据不成功会崩溃。 为了安全起见放入数
据统一使用put
(3)获取队列里的数据
value = queue.get()
print(value)
注意: 如果队列空了,那么使用get取值会等待,直到队列有数据才
能获取数据。如果队列空了, 使用get_nowait不会等待,如果取值不
成功则崩溃。为了安全起见获取数据使用get。
(4)获取队列的个数,注意:mac不能使用此方法
queue_size = queue.qsize()
(5)判断队列是否为空
queue.empty()——不靠谱
如果队列为空,返回True,反之False
注意:判断队列是否为空的时候,不会等数据全部写入到队列以
后再判断,有可能出现获取的队列是空的
解决办法:保证数据全部写入到队列里面以后再去做判断
① 延时
time.sleep(0.001)
②根据个数判断
if queue.qsize() == 0:
print("队列为空")
else:
print("队列不为空")
3.3.7进程池
池子里面放的都是进程,进程的创建是进程池根据执行任务的情况自动创建进程,进程池会合理利用现有的进程完成多任务
import multiprocessing
import time
# 拷贝任务
def copy_work():
print("复制中....,", multiprocessing.current_process().pid)
# 默认进程池创建的进程是守护主进程,那么主进程退出进程池中的子进程就直接销毁
time.sleep(1)
if __name__ == '__main__':
# 创建进程池, 3:表示进程池中最大进程的个数
pool = multiprocessing.Pool(3)
# 使用进程池执行对应任务,默认大批量的复制任务让进程池帮我们完成
for i in range(10):
# 同步执行对应的任务,是一个任务执行完成另外一个任务才能执行
# pool.apply(copy_work)
# 异步执行,多个任务一起执行,任务之间不会等待
pool.apply_async(copy_work)
#注意:异步执行要有下面的操作:关闭进程池和等待
# 关闭进程池,表示进程池以后不再接收对应的任务了,主进程会根据现有进程池的任务执行完成以后程序就可以退出了
pool.close()
# 主进程等待进程池把任务执行完成以后程序再退出
pool.join()
# 提醒: 进程池会根据任务执行情况自动创建进程,进程池会尽量少创建进程,可以利用现有的进程完成多任务
3.3.8进程池中的Queue
(1)如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue()
(2)创建进程池中queue
queue = multiprocessing.Manager().Queue(3)
(3)使用同步的方式执行任务
# 参数要使用元组传参
pool.apply(write_data, (queue, ))
(4)使用异步的方式去执行任务 ,异步执行任务会返回一个ApplyResult 对象
result = pool.apply_async(write_data, (queue,))
print(result)
(5)扩展:主进程等待异步任务执行完成以后代码再继续往下执行
result.w·ait()
pool.apply_async(read_data, (queue,))
# 异步执行任务,主进程需要等待进程池执行完成以后程序再退出
pool.close()
pool.join()
3.3.9案例:文件夹拷贝器
import os
import shutil
import multiprocessing
import time
# 拷贝文件的任务
def copy_work(src_dir, dst_dir, file_name):
print(multiprocessing.current_process().pid)
# 源文件的路径
src_file_path = src_dir + "/" + file_name
# 目标文件的路径
dst_file_path = dst_dir + "/" + file_name
# 打开目标文件
with open(dst_file_path, "wb") as dst_file:
# 打开源文件获取源文件的数据把数据写入到目标文件里面
with open(src_file_path, "rb") as src_file:
while True:
# 读取数据
src_file_data = src_file.read(1024)
if src_file_data:
# 把源文件的数据写入到目标文件里面
dst_file.write(src_file_data)
else:
break
time.sleep(0.5)
if __name__ == '__main__':
# 源目录
src_dir = "test"
# 目标目录
dst_dir = "/home/python/Desktop/test"
# 判断文件夹是否存在
if os.path.exists(dst_dir):
# 删除目标目录
shutil.rmtree(dst_dir)
# 创建文件夹
os.mkdir(dst_dir)
# 获取源目录的文件列表
file_name_list = os.listdir(src_dir)
# 创建进程池
pool = multiprocessing.Pool(3)
# 遍历文件列表获取对应的文件名
for file_name in file_name_list:
# 使用进程池完成多任务的文件拷贝
pool.apply_async(copy_work, (src_dir, dst_dir, file_name))
# 关闭进程池
pool.close()
# 主进程等待进程池执行完成以后程序再退出
pool.join()