进程、进程的使用、进程注意点、进程间通信-Queue、进程池Pool、进程与线程对比、文件夹拷贝器-多任务
1.进程介绍
多进程本质是创建一个进程默认创建一个线程,任务本身是由线程完成的。多进程可以处理多任务,但是每创建一个进程都要向系统索要运行资源,相比线程来说资源占用比较多成本比较大
<1>进程的概念
进程:通俗理解一个运行的程序或者软件,进程是操作系统资源分配的基本单位。
注意:一个程序至少有一个进程,一个进程至少有一个线程,多进程可以完成多任务.
<2>进程的状态
工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,因此导致了有不同的状态
就绪态:运行的条件都已经满足,正在等待cpu执行
执行态:cpu正在执行其功能
等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态
小结:
一个进程默认有一个线程,进程里面可以创建线程,线程是依附在进程里面的,没有进程就没有线程。
2.进程的使用
<1>多进程完成多任务
导入进程模块
import multiprocessing
<2>Process进程类的语法结构如下:
Process([group [, target [, name [, args [, kwargs]]]]])
group:指定进程组,目前只能使用None
target:执行的目标任务名
name:进程名字
args:以元组方式给执行任务传参
kwargs:以字典方式给执行任务传参
Process创建的实例对象的常用方法:
start():启动子进程实例(创建子进程)
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
pid:当前进程的pid(进程号)
<3>多进程完成多任务代码
import multiprocessing
import time
def run_proc():
"""子进程要执行的代码"""
while True:
print("----2----")
time.sleep(1)
if __name__=='__main__':
# 创建子进程
sub_process = multiprocessing.Process(target=run_proc)
# 启动子进程
sub_process.start()
while True:
print("----1----")
time.sleep(1)
<4>获取当前进程、当前进程pid、父进程pid(paternal pid)、杀死对应的进程
获取当前进程
multiprocessing.current_process()
获取当前进程的编号
multiprocessing.current_process().pid
os.getpid()
获取父进程的编号
os.getppid()
根据进程编号杀死对应的进程
os.kill(os.getpid(), 9)
import multiprocessing
import time
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)
if __name__ == '__main__':
# 查看当前进程
current_process = multiprocessing.current_process()
print("main:", current_process)
# 获取当前进程的编号
print("main进程的编号:", current_process.pid)
# 创建子进程
sub_process = multiprocessing.Process(target=work)
# 启动进程
sub_process.start()
# 主进程执行打印信息操作
for i in range(20):
print("我在主进程中执行...")
time.sleep(0.2)
<5>给子进程指定的函数传递参数
import multiprocessing
# 显示人员信息
def show_info(name, age):
print(name, age)
if __name__ == '__main__':
# 创建子进程
# 1. group:进程组,目前必须使用None,一般不用设置
# 2. target:执行目标函数
# 3. name: 进程名称
# 4. args: 以元组方式给函数传参
# 5. kwargs: 以字典方式给函数传参
sub_process = multiprocessing.Process(target=show_info, name="myprocess", args=("古力娜扎", 18))
# 启动进程
sub_process.start()
# sub_process = multiprocessing.Process(target=show_info, name="myprocess", kwargs={"name": "貂蝉", "age": 20})
#
# # 启动进程
# sub_process.start()
3.进程注意点
<1>进程之间不共享全局变量
import multiprocessing
import time
# 定义全局变量
my_list = list()
# 写入数据
def write_data():
for i in range(5):
my_list.append(i)
time.sleep(0.2)
print("write_data:", my_list)
# 读取数据
def read_data():
print(my_list)
if __name__ == '__main__':
# 创建写入数据的进程
write_process = multiprocessing.Process(target=write_data)
read_process = multiprocessing.Process(target=read_data)
write_process.start()
# 主进程等待写入进程执行完成以后代码 再继续往下执行
write_process.join()
read_process.start()
执行结果:
write_data: [0, 1, 2, 3, 4]
read_data: []
注意:
创建子进程其实是对主进程(除了main方法程序入口以外的,Windows如果创建子进程不用main程序入口会无限循环创建进程导致程序报错)所有资源进行拷贝,进程之间相互独立,访问的全局变量不是同一个,所以进程之间不共享全局变量
多进程之间可能出现相同名字的全局变量,但是不是同一个全局变量,是不同的全局变量,只不过名字相同而已
<2>主进程会等待所有的子进程执行完成程序再退出
import multiprocessing
import time
# 测试子进程是否执行完成以后主进程才能退出
def work():
for i in range(10):
print("工作中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
work_process = multiprocessing.Process(target=work)
work_process.start()
# 让主进程等待1秒钟
time.sleep(1)
print("主进程执行完成了啦")
总结: 主进程会等待所有的子进程执行完成以后程序再退出
<3>销毁子进程的代码
设置守护主进程,主进程退出后子进程直接销毁,不再执行子进程中的代码
子进程名.daemon = True
让子进程直接销毁,表示终止执行, 主进程退出之前,把所有的子进程直接销毁就可以了
子进程名.terminate()
import multiprocessing
import time
# 测试子进程是否执行完成以后主进程才能退出
def work():
for i in range(10):
print("工作中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
work_process = multiprocessing.Process(target=work)
# 设置守护主进程,主进程退出后子进程直接销毁,不再执行子进程中的代码
# work_process.daemon = True
work_process.start()
# 让主进程等待1秒钟
time.sleep(1)
print("主进程执行完成了啦")
# 让子进程直接销毁,表示终止执行, 主进程退出之前,把所有的子进程直接销毁就可以了
work_process.terminate()
# 总结: 主进程会等待所有的子进程执行完成以后程序再退出
小结:
进程之间不共享全局变量
主进程会等待所有的子进程执行完成程序再退出
4.进程间通信-Queue
<1>Queue的使用
可以使用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)
# queue.get_nowait()提示: 如果队列空了 ,不需要等待队列有值,但是如果取值的时候发现队列空了直接崩溃
# 建议大家: 向队列取值使用get
# value = queue.get_nowait()
# print(value)
说明:
初始化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);
<2>消息队列Queue完成进程间通信的演练
我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
import multiprocessing
import time
# 写入数据
def write_data(queue):
for i in range(10):
if queue.full():
print("队列满了")
break
queue.put(i)
time.sleep(0.2)
print(i)
# 读取数据
def read_data(queue):
while True:
# 加入数据从队列取完了,那么跳出循环
if queue.qsize() == 0:
print("队列空了")
break
value = queue.get()
print(value)
if __name__ == '__main__':
# 创建消息队列
queue = multiprocessing.Queue(5)
# 创建写入数据的进程
write_process = multiprocessing.Process(target=write_data, args=(queue,))
# 创建读取数据的进程
read_process = multiprocessing.Process(target=read_data, args=(queue,))
# 启动进程
write_process.start()
# 主进程等待写入进程执行完成以后代码再继续往下执行
write_process.join()
read_process.start()
小结:
从队列取值使用get方法,向队列放入值使用put方法
消息队列判断队列是否为空不可靠,可以使用延时和根据个数进行判断
5.进程池Pool
进程池的好处:
根据任务自动创建进程
合理利用指定进程完成多任务
<1>进程池的概念
池子里面放的是进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务
(根据任务执行情况自动创建进程,并不是一开始就创建最大数量的进程)
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务.
<2>进程池同步执行任务
进程池同步执行任务表示进程池中的进程在执行任务的时候一个执行完成另外一个才能执行,如果没有执行完会等待上一个进程执行
进程池同步实例代码
import multiprocessing
import time
# 拷贝任务
def work():
print("复制中...", multiprocessing.current_process().pid)
time.sleep(0.5)
if __name__ == '__main__':
# 创建进程池
# 3:进程池中进程的最大个数,如果不设置,默认最大进程个数为电脑CPU的核数
pool = multiprocessing.Pool(3)
# 模拟大批量的任务,让进程池去执行
for i in range(5):
# 循环让进程池执行对应的work任务
# 同步执行任务,一个任务执行完成以后另外一个任务才能执行
pool.apply(work)
<3>进程池异步执行任务
进程池异步执行任务表示进程池中的进程同时执行任务,进程之间不会等待
异步执行,任务执行不会等待,多个任务一起执行
进程池名.apply_async(任务名)
# 关闭进程池,意思告诉主进程以后不会有新的任务添加进来
进程池名.close()
# 主进程等待进程池执行完成以后程序再退出
进程池名.join()
进程池异步实例代码
# 进程池:池子里面放的进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务
import multiprocessing
import time
# 拷贝任务
def work():
print("复制中...", multiprocessing.current_process().pid)
# 获取当前进程的守护状态
# 提示:使用进程池创建的进程是守护主进程的状态true,默认自己通过Process创建的进程不是守护主进程的状态false
# 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之后使用;
6.进程、线程对比
<1>功能对比
进程,能够完成多任务,比如 在一台电脑上能够同时运行多个QQ
线程,能够完成多任务,比如 一个QQ中的多个聊天窗口
<2>定义对比
进程是系统进行资源分配基本单位,每启动一个进程操作系统都需要为其分配运行资源。
线程是CPU调度基本单位,是运行程序中的一个执行分支。
总结:进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
<3>关系对比
线程是依附在进程里面的,没有进程就没有线程
一个进程默认提供一条线程,进程可以创建多个线程
<4>区别
进程之间不共享全局变量
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
创建进程的资源开销要比创建线程的资源开销要大
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
线程不能够独立执行,必须依存在进程中
多进程开发比单进程多线程开发稳定性要强
<5>优缺点
多进程:
优点:可以用多核
缺点:资源开销大
多线程:
优点:资源开销小
缺点:不能使用多核
7.文件夹拷贝器-多任务
<1>功能要求
使用进程池完成多任务文件夹的拷贝
import os
import shutil
import multiprocessing
# import time
# 文件拷贝任务
def copy_work(src_dir, dst_dir, file_name):
# 查看进程对象
pid = multiprocessing.current_process().pid
print(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:
# 使用进程池执行拷贝任务,使用*args和**kwagrs传参数!此处选择元组
pool.apply_async(copy_work, (src_dir, dst_dir, file_name))
# 关闭进程池
pool.close()
# 主进程等待进程池执行完成以后程序再退出
pool.join()
小结:
进程池在执行任务的时候会尽量少创建进程,合理利用现有进程完成多任务,这样可以减少资源开销