我们可以这样理解进程与线程,进程相当于是电脑上运行的qq,qq音乐,lol这样的不同的程序,而线程是qq上的不同的聊天窗口。根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
而进程和程序的差别则是:
进程:正在执行的程序
程序:没有执行的代码,是一个静态的
使用进程实现多任务
multiprocessing模块就是跨平台的多进程模块,提供了一个Process类来代表一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情。
import multiprocessing
def sing():
for i in range(3):
print('正在唱歌...%s' % i)
def dance():
for i in range(3):
print('正在跳舞...%s' % i)
def main():
p1 = multiprocessing.Process(target=sing)
p2 = multiprocessing.Process(target=dance)
p1.start()
p2.start()
if __name__ == '__main__':
main()
和线程的代码大致相同,就是将几个位置改一下。
进程间通信-Queue
import multiprocessing
a = 1
def demo1():
global a
a += 1
def demo2():
# 进程是不共享的 线程是共享
print(a)
if __name__ == '__main__':
t1 = multiprocessing.Process(target=demo1)
t2 = multiprocessing.Process(target=demo2)
t1.start()
t2.start()
运行上述代码我们会发现打印出的结果为1,可知共享全局变量不适用于多进程编程
要完成进程间的通信,我们需要用到队列Queue,进程中的队列用法:
from multiprocessing import Queue
# 创建队列 最多可以存放3条数据
q = Queue(3)
# 存数据
q.put(1)
q.put("juran")
q.put([11, 22])
# print(q.qsize()) 查看队列长度
# q.put({"name": "juran"}) 此时队列已满会堵塞
# q.put_nowait({"name": "juran"}) 使用nowait的话会直接抛出异常
# print(q.full()) 查看队列是否满了
print(q.get()) # 从队列里取值
print(q.get())
print(q.get())
# 堵塞
# print(q.get())
# print(q.get_nowait())
# print(q.empty()) 查看队列是否为空
队列遵循的是先进先出原则。
import multiprocessing
from multiprocessing import Queue
def demo1(q):
q.put('a')
def demo2(q):
data = q.get()
print(data)
if __name__ == '__main__':
q = Queue()
t1 = multiprocessing.Process(target=demo1, args=(q,))
t2 = multiprocessing.Process(target=demo2, args=(q,))
t1.start()
t2.start()
如上述代码引入进程中的Queue队列并创建一个队列,就能达到进程间通信的目的,值得注意的是,这里用的是进程的通信,即需要从multiprocessing库中导入的Queue,而不是从queue中导入的Queue。
进程池
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,但是如果是上百甚至上千个目标,手动的去创建的进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求,但是如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务
from multiprocessing import Pool
import os, time, random
def worker(msg):
t_start = time.time()
print('%s开始执行,进程号为%d' % (msg, os.getpid()))
time.sleep(random.random() * 2)
t_stop = time.time()
# 0.2f
print(msg, "执行完成,耗时%0.2f" % (t_stop - t_start))
if __name__ == '__main__':
po = Pool(4) # 定义一个进程池 4个进程
for i in range(0, 10):
po.apply_async(worker, (i,))
print("--start--")
# 关闭进程池 不在接收新的请求
po.close()
# 等待子进程执行完成
po.join()
print("--end--")
我们通过multiprocessing中的Pool来创建进程池,括号中的参数为创建的进程个数,因为代码执行效率高,所以我们给代码的运行添加一点延迟方便观察结果,最后通过代码运行的结果
可以发现进程号就在4个里面变化,当一个进程号里的进程运行结束,就运行下一个进程。
进程池间的进程通信
import multiprocessing
def demo1(q):
# 进程池里面的进程 如果出现异常 不会主动抛出
try:
q.put('a')
except Exception as e:
print(e)
def demo2(q):
try:
data = q.get()
print(data)
except Exception as e:
print(e)
if __name__ == '__main__':
# q = Queue() 不能完成进程之间的通信
# q = multiprocessing.Queue() # 进程间通信
q = multiprocessing.Manager().Queue() # 进程池中的进程通信
po = multiprocessing.Pool(2)
po.apply_async(demo1, args=(q,))
po.apply_async(demo2, args=(q,))
po.close()
po.join()
进程池间的进程通信也是通过队列来完成,不过与进程间通信使用的队列有所不同,总结如下。
q = Queue() 不能完成进程之间的通信
q = multiprocessing.Queue() # 进程间通信
q = multiprocessing.Manager().Queue() # 进程池中的进程通信
多任务文件夹复制
1 获取用户要copy的文件夹的名次
2 创建一个新的文件夹
3 获取文件夹的所有的待copy的文件名字
4 创建进程池
5 向进程池中添加拷贝任务
import multiprocessing
import os
def copy_file(q, file_name, new_folder_name, old_folder_name):
"""完成文件拷贝"""
with open(old_folder_name + "/" + file_name, "rb") as f:
content = f.read()
# 保存到新的文件夹中
new_file = open(new_folder_name + "/" + file_name, "wb")
new_file.write(content)
new_file.close()
q.put(file_name)
def main():
# 获取用户要复制的文件夹名字 test
old_folder_name = input("请输入要复制的文件夹名字:")
# 创建一个新的文件夹 test[复件]
new_folder_name = old_folder_name + "复件"
if not os.path.exists(new_folder_name):
os.mkdir(new_folder_name)
# 获取文件夹的所有待拷贝的文件名字
file_names = os.listdir(old_folder_name)
# 创建进程池
po = multiprocessing.Pool(5)
# 创建队列
q = multiprocessing.Manager().Queue()
# 添加拷贝任务
for file_name in file_names:
po.apply_async(copy_file, args=(q, file_name, new_folder_name, old_folder_name))
po.close()
po.join()
if __name__ == '__main__':
main()