一、进程:
一个程序运行起来后,代码+用到的资源称之为进程,它是操作系统分配资源的基本单元。其也会有并发与并行的状态,与多线程不同的是,多进程会充分利用CPU的资源来执行任务。
进程的状态:
当任务数大于CPU的核数时,部分任务在执行,部分任务在等待执行,因此就会产生不通的状态:
1.就绪状态:运行条件以满足,等待CPU执行。
2.执行状态:CPU正在执行其任务。
3.等待状态:等待某些条件满足,例如一个程序sleep了。
multiprocessing模块
python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing,其支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件
Process:
使用方法与线程Thread类一样如下,需要注意的是进程的创建与启动必须放在__main__下面,否则会造成循环加载任务代码的问题。
from multiprocessing import Process
a = 0
def work(name):
global a
for i in range(100):
a += 1
print(f"{name}----", a)
def work1(name):
global a
for i in range(100):
a += 1
print(f"{name}----", a)
if __name__ == '__main__':
p = Process(target=work, args=('MING',)) # 创建进程对象,通过args传入任务参数
p1 = Process(target=work1,args=('CHAZ',),daemon=True) #设置进程是否为守护进程,如果是守护进程,则与主进程一起关闭。
p.start() # 启动进程
p1.start()
p.join() # 等待进程执行完成
p1.join()
print('a:', a)
>>>MING---- 100
>>>CHAZ---- 100
>>>a: 0
os.getpid()查看进程id,os.getppid(),查看父进程id
import time,os
from multiprocessing import Process
def fun():
time.sleep(2)
print(f"fun--进程id{os.getpid()},父进程id{os.getppid()}")
def fun1():
time.sleep(2)
print(f"fun1--进程id{os.getpid()},父进程id{os.getppid()}")
if __name__ == '__main__':
p = Process(target=fun)
p1 = Process(target=fun1)
p.start()
p1.start()
p.join()
p1.join()
print(f'主进程--{os.getpid()}')
>>> fun--进程id18188,父进程id14604
>>> fun1--进程id19256,父进程id14604
>>> 主进程--14604
从例1中可以看到进程之间的信息是独立的,并没有共享,如果想实现进程之间的通信可以使用Queue模块,(需要注意的是并非queue模块中的Queue,此队列只能用来在一个进程中的多个线程之间使用)。而进程中的Queue可以在多个进程之间跨进程传输数据。
导入:
from multiprocessing import Queue
举例:
一个列表中有10个url地址,每个地址请求一次,使用2个进程程去发送这 10个请求(假设请求每个地址需要0.5秒)
在不使用Queue的情况下:
from multiprocessing import Process
import time
li = ['www.baidu.com-{}'.format(i) for i in range(10)]
def work():
while li:
url = li.pop()
print("请求地址:", url)
time.sleep(0.5)
if __name__ == '__main__':
t = Process(target=work)
t1 = Process(target=work)
t.start()
t1.start()
t.join()
t1.join()
运行结果:
请求地址: www.baidu.com-9
请求地址: www.baidu.com-9
请求地址: www.baidu.com-8
请求地址: www.baidu.com-8
请求地址: www.baidu.com-7
请求地址: www.baidu.com-7
请求地址: www.baidu.com-6
.....
此时每个进程拿到的列表是独立的,并不是共享的才造成上面的问题。如下,使用进程中的队列解决这一问题:
from multiprocessing import Process, Queue
import time
def work(urls):
while not urls.empty(): # 判断队列是否为空
url = urls.get() # 获取队列中的值
print("请求地址:", url)
time.sleep(0.5)
if __name__ == '__main__':
urls = Queue() # 创建队列
for i in range(10):
urls.put(f"www.baidu.com-{i}") # 队列中添加数据
t = Process(target=work, args=(urls,)) # 创建进程对象,将队列作为函数的参数
t1 = Process(target=work, args=(urls,))
t.start()
t1.start()
t.join()
t1.join()
运行结果:
请求地址: www.baidu.com-0
请求地址: www.baidu.com-1
请求地址: www.baidu.com-2
请求地址: www.baidu.com-3
请求地址: www.baidu.com-4
请求地址: www.baidu.com-5
....
二、进程池
当需要创建的子进程数量不多时,可以直接使用Process动态生成多个进程,但如果是上百个甚至更多的目标,通过手动创建进程的工作量巨大,此时可以用到multiprocessing模块提供的Pool方法。
创建进程池时候可以指定最大进程数,当有新的请求要提交到Pool中时,会判断Pool中有没有达到最大进程数,如果达到,该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。
进程池之间的通讯使用Manager中的Queue()。
from multiprocessing import Pool
import time
def func(msg):
print("msg:", msg)
time.sleep(3)
if __name__ == "__main__":
po = Pool(3) # 创建进程池
for i in range(4):
msg = f"hello{i}"
po.apply_async(func, (msg,)) # 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
print("Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~")
po.close()
po.join() # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
print("Sub-process(es) done.")
ProcessPoolExecutor:与线程池ThreadPoolExecutor 原理及使用方法是一样的,通过ProcessPoolExecutor创建线程池,submit提交任务到线程池。max_workers指定线程池中的数量
import time
from concurrent.futures import ProcessPoolExecutor
def work(name):
for i in range(5):
time.sleep(1)
print(f"{name}----{i}----")
#学习中遇到问题没人解答?小编创建了一个Python学习交流群:711312441
with ProcessPoolExecutor(max_workers=4) as tp: # 创建进程池
tp.submit(work, 'MING') # 往进程提交任务
tp.submit(work, 'HOUX')
tp.shutdown() # 等待进程池中所有的任务执行完成
print("---主进程---")
运行结果:
MING----0----HOUX----0----
MING----1----HOUX----1----
HOUX----2----MING----2----
MING----3----HOUX----3----
HOUX----4----MING----4----
---主进程---