参考文章:http://www.cnblogs.com/lipijin/p/3709903.html 对作者表示感谢
多进程创建
-
os.fork()可以创建一个子进程,子进程的内容完全从父进程拷贝而来。同时,调用一次os.fork()会返回两个值,当处于子进程时,返回值为0;当处于父进程时,返回值为子进程ID。同时,通过os.getpid()可以得到当前进程的ID,通过os.getppid()可以得到当前进程父进程的ID。
对os.fork()的理解
os.fork()会创建两个进程,一个是父进程,一个是子进程,从os.fork()返回两个值也能体现出这一点。这父子进程的特点是,子进程会获得父进程的数据空间、堆、栈等资源,所以注意,父子进程不共享存储空间,子进程会负责父进程的资源然后放在一个新的内存空间中,但是这两个进程共享代码段(这里是进程管理的内容,进程实体由PCB、数据段、代码段组成)。
除此之外,还想好好谈一下对于进程的理解。一个进程实体是由三部分组成:PCB、数据段、程序段。所以一个进程对应一个待执行的程序。通常通过传递对应程序的地址来将程序分配给一个进程。例如
def pr1(process_name):
for i in range(5):
print (process_name, os.getpid()) # 打印出当前进程的id
time.sleep(random.random())
p1 = Process(target=pr1, args=('process_name1',))
就是将pr1这个程序传给p1这个进程,所以在这个进程内就会完成一个for循环。注意整个for循环都会在pr1这个进程内完成。
还有一个出现了for循环的进程的调用。容易和上面那部分混淆,混淆的原因就是因为对进程的概念、运行机制不够理解。
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
for i in range(5):
p.apply_async(long_time_task, args=(i,))
在这里,是利用了for循环对Pool中的进程完成了5次的执行。所以这里实际上是有5个进程。
最终决定输出结果的不单单是程序本身,还有进程数(os.fork()子进程将父进程进行拷贝)。其余的情况就是由程序所决定的,只不过通过多进程机制将程序的各个部分交付给不同的进程进行处理。
-
还可以采用multiprocessiing模块中的Process类,来创建子进程。因为os.fork()的方法只能在UNIX/LINUX内核下使用,是比较接近底层的方法。为了在其他平台(Windows)下使用,所以使用multiprocessing模块来实现跨平台的多进程
使用multiprocessing模块中的Process类,一个进程对象对于类的一个实例。所以创建一个Process类实例,就相当于创建了一个进程,使用Process()函数,向其传入target=即进程对于的程序等参数,返回一个进程对象,再调用该进程对象的start()方法,即可运行该进程。
再使用该进程对象的join()方法,join()方法用于等待子进程结束后再继续往下运行,通常用于进程间的同步。官方文档的解释是:阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程。
注意,在main中的是主进程,或者说是父进程,在main中创建的新的进程为子进程。例如使用Process类创建的进程的对象。
以下面的代码为例
# encoding:utf-8
from multiprocessing import Process
import os, time
# 线程启动后实际执行的代码块
def pr1(process_name):
for i in range(5):
print (process_name, os.getpid()) # 打印出当前进程的id
time.sleep(1)
def pr2(process_name):
for i in range(5):
print (process_name, os.getpid()) # 打印出当前进程的id
time.sleep(1)
if __name__ == "__main__":
print ("main process run...")
# target:指定进程执行的函数
# args:传入target参数对应函数的参数,需要使用tuple
p1 = Process(target=pr1, args=('process_name1',))
p2 = Process(target=pr2, args=('process_name2',))
p1.start()
p2.start()
p1.join()
p2.join()
print ("main process ran all lines...")
以下为运行结果:
main process run...
('process_name1', 3798)
('process_name2', 3799)
('process_name1', 3798)
('process_name2', 3799)
('process_name1', 3798)
('process_name2', 3799)
('process_name1', 3798)
('process_name2', 3799)
('process_name1', 3798)
('process_name2', 3799)
main process ran all lines...
可以看到,main函数中的内容为父进程,其起始和结束标志为“main process run…”和“main process ran all lines…”。由此结果可知,开始时先运行父进程,其中有p1、p2两个子进程的创建,然后依次通过px.start()方法运行子进程p1和p2。由于调用了px.join()方法,该方法会阻塞父进程,直到子进程全部运行完成后才会继续运行父进程。
通过运行px.join()注释后的代码得到的结果验证join()方法的作用:
# encoding:utf-8
from multiprocessing import Process
import os, time
# 线程启动后实际执行的代码块
def pr1(process_name):
for i in range(5):
print (process_name, os.getpid()) # 打印出当前进程的id
time.sleep(1)
def pr2(process_name):
for i in range(5):
print (process_name, os.getpid()) # 打印出当前进程的id
time.sleep(1)
if __name__ == "__main__":
print ("main process run...")
# target:指定进程执行的函数
# args:传入target参数对应函数的参数,需要使用tuple
p1 = Process(target=pr1, args=('process_name1',))
p2 = Process(target=pr2, args=('process_name2',))
p1.start()
p2.start()
# p1.join()
# p2.join()
print ("main process ran all lines...")
运行结果
main process run...
main process ran all lines...
('process_name1', 3911)
('process_name2', 3912)
('process_name1', 3911)
('process_name2', 3912)
('process_name1', 3911)
('process_name2', 3912)
('process_name1', 3911)
('process_name2', 3912)
('process_name1', 3911)
('process_name2', 3912)
其中p1、p2为两个子进程,main为父进程,如果将px.join()注释,会发现运行结果中“main process ran all lines…”不是出现在结果的末尾,这就表示实际上未等到子进程结束,主进程先结束了。而如果使用的px.join()方法,他能使得父进程阻塞,等待子进程结束后父进程才得以继续运行,那么就会发现“main process ran all lines…”会出现在结果的最后一条。
-
如果要启动大量的子进程,可以用进程池Pool的方式批量创建子进程
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
- 这里long_time_task()定义了子进程。
- 其中time.time()会返回当前的时间,所以通过两次调用time.time()再求得二者差值就可以得到这段时间的长度。
- 在主进程中,创建一个Pool对象,其中传入的参数是这个Pool的大小,当没有参数传递时,这个大小值默认为CPU核数。每一次都要等一个Pool内的进程全都结束后,才可将剩下的还未处理的进程加入Pool中进行处理。所以当有5个子进程而Pool的大小只有4的时候,只会先把前四个子进程先处理完 ,之后再对第五个子进程进行处理。
代码为例:range(5)
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random())
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
运行结果:
Parent process 5249.
Waiting for all subprocesses done...
Run task 1 (5251)...
Run task 0 (5250)...
Run task 2 (5252)...
Run task 3 (5253)...
Task 0 runs 0.54 seconds.
Run task 4 (5250)...
Task 1 runs 0.68 seconds.
Task 3 runs 0.78 seconds.
Task 2 runs 0.80 seconds.
Task 4 runs 0.66 seconds.
All subprocesses done.
Process finished with exit code 0
可以看到,在task0、1、2、3执行完之后,才开始执行task4
-
Pool(4).close()表示关闭Pool,这样就不允许有新的Process加入Pool中
-
Pool(4).join()表示主进程阻塞,要等待子进程执行完毕后才能继续执行。join()方法需要在close()或者terminate()使用完之后再使用
- Pool(4).apply_async(target=, args=())的apply_async()方法表示使用大小为4的Pool去完成target对应的程序,其中args为待传入target对应的程序的参数的元组。在介绍apply_async()之前,先介绍一下apply()
- apply():apply是阻塞的。首先主进程开始运行,碰到子进程,操作系统切换到子进程,等待子进程运行结束后,在切换到另外一个子进程,直到所有子进程运行完毕。然后在切换到主进程,运行剩余的部分。这样跟单进程串行执行没什么区别。
- apply_async():apply_async 是异步非阻塞的。即不用等待当前进程执行完毕,随时根据系统调度来进行进程切换。首先主进程开始运行,碰到子进程后,主进程仍可以先运行,等到操作系统进行进程切换的时候,在交给子进程运行。可以做到不等待子进程执行完毕,主进程就已经执行完毕,并退出程序。通常主进程对应的主程序很简洁,所以主进程很快就运行完毕了,那么很可能还没来得及将主程序中调用的放在子进程中运行的部分执行完,主程序就先结束了,这样可能会带来错误。所以往往将apply_async和join结合使用,等到所有子程序运行完毕后,主程序再继续运行。
可以用代码来说明:
apply():
###apply():
import time
from multiprocessing import Pool
def run(count):
print('子进程编号:%s' % count)
time.sleep(2)
print('子进程%s结束' % count)
if __name__ == "__main__":
print("开始执行主进程")
start_time = time.time()
# 使用进程池创建子进程
pool = Pool(4)
print("开始执行子进程")
for i in range(4):
pool.apply(run, (i,))
print("主进程结束,总耗时%s" % (time.time() - start_time))
运行结果:
Parent process 5217.
Run task 0 (5218)...
Task 0 runs 0.57 seconds.
Run task 1 (5219)...
Task 1 runs 0.23 seconds.
Run task 2 (5220)...
Task 2 runs 0.92 seconds.
Run task 3 (5221)...
Task 3 runs 0.78 seconds.
Waiting for all subprocesses done...
All subprocesses done.
Process finished with exit code 0
apply_async():
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random())
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(4):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
运行结果:
Parent process 5208.
Waiting for all subprocesses done...
Run task 0 (5209)...
Run task 1 (5210)...
Run task 2 (5211)...
Run task 3 (5212)...
Task 2 runs 0.02 seconds.
Task 1 runs 0.59 seconds.
Task 3 runs 0.87 seconds.
Task 0 runs 0.97 seconds.
All subprocesses done.
Process finished with exit code 0
观察二者的运行结果即可知晓。