编写完毕的代码,在没有运行的时候,称之为程序
正在运行着的代码,就成为进程
进程(process)是正在运行的程序的实例,但一个程序可能会产生多个进程。
进程,除了包含代码以外,还有需要运行的环境等,所以和程序是有区别的。
1.fork()
Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:
import os# 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以pid = os.fork()if pid == 0: print('哈哈1')else: print('哈哈2')
运行结果:
说明:
- 程序执行到os.fork()时,操作系统会创建一个新的进程(子进程),然后复制父进程的所有信息到子进程中
- 然后父进程和子进程都会从fork()函数中得到一个返回值,在子进程中这个值一定是0,而父进程中是子进程的 id号
在Unix/Linux操作系统中,提供了一个fork()系统函数,它非常特殊。
普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。
这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
getpid()、getppid()
import osrpid = os.fork()if rpid<0: print("fork调用失败。")elif rpid == 0: print("我是子进程(%s),我的父进程是(%s)"%(os.getpid(),os.getppid())) x+=1else: print("我是父进程(%s),我的子进程是(%s)"%(os.getpid(),rpid))print("父子进程都可以执行这里的代码")
运行结果:
我是父进程(19360),我的子进程是(19361)父子进程都可以执行这里的代码我是子进程(19361),我的父进程是(19360)父子进程都可以执行这里的代码
需要注意的是,虽然子进程复制了父进程的代码段和数据段等,
但是一旦子进程开始运行,子进程和父进程就是相互独立的,它们之间不再共享任何数据。
多次fork
父进程、子进程执行顺序没有规律,完全取决于操作系统的调度算法
2.multiprocessing
Python 提供了一个 multiprocessing 模块,利用它,我们可以来编写跨平台的多进程程序,但需要注意的是 multiprocessing 在 Windows 和 Linux 平台的不一致性:一样的代码在 Windows 和 Linux 下运行的结果可能不同。因为 Windows 的进程模型和 Linux 不一样,Windows 下没有 fork。
先来看一个简单的例子,该例子演示了在主进程中启动一个子进程,并等待其结束,代码如下:
import osfrom multiprocessing import Process # 模块引入 Process# 子进程要执行的代码def child_proc(name): print 'Run child process %s (%s)...' % (name, os.getpid())if __name__ == '__main__': print 'Parent process %s.' % os.getpid() p = Process(target=child_proc, args=('test',)) # target 指定了进程要执行的函数 # args 指定了参数 print 'Process will start.' p.start() # start 方法开始执行该子进程 p.join() # 阻塞子进程以外的所有进程(这里指父进程) print 'Process end.'
multiprocessing 模块引入了 Process,Process 是一个用于创建进程对象的类,
target:指定了进程要执行的函数,
args:指定了参数。
start 方法:开始执行该子进程,
join 方法:该方法用于阻塞子进程以外的所有进程(这里指父进程),当子进程执行完毕后,父进程才会继续执行,它通常用于进程间的同步。
可以看到,用上面这种方式来创建进程比直接使用 fork 更简单易懂。现在,让我们看下输出结果:
Parent process 7170.Process will start.Run child process test (10075)...Process end.
Process语法结构如下:
Process([group [, target [, name [, args [, kwargs]]]]])
- target
- args
- kwargs
- name
- group
Process类常用方法:
- is_alive()
- join([timeout])
- start()
- run():如果没有给定target参数,对这个对象调用start()方法时,就将执行对象中的run()方法;
- terminate()
Process类常用属性:
- name:当前进程实例别名,默认为Process-N,N为从1开始递增的整数;
- pid
进程的创建-Process子类
Process语法结构如下:Process([group [, target [, name [, args [, kwargs]]]]])target:表示这个进程实例所调用对象;args:表示调用对象的位置参数元组;kwargs:表示调用对象的关键字参数字典;name:为当前进程实例的别名;group:大多数情况下用不到;Process类常用方法:is_alive():判断进程实例是否还在执行;join([timeout]):是否等待进程实例执行结束,或等待多少秒;start():启动进程实例(创建子进程);run():如果没有给定target参数,对这个对象调用start()方法时,就将执行对象中的run()方法;terminate():不管任务是否完成,立即终止;Process类常用属性:name:当前进程实例别名,默认为Process-N,N为从1开始递增的整数;pid:当前进程实例的PID值;进程的创建-Process子类
3.进程池Pool
Python 提供了进程池的方式,让我们批量创建子进程,让我们看一个简单的示例:
import os, timefrom multiprocessing import Pooldef foo(x): print 'Run task %s (pid:%s)...' % (x, os.getpid()) time.sleep(2) print 'Task %s result is: %s' % (x, x * x)if __name__ == '__main__': print 'Parent process %s.' % os.getpid() p = Pool(4) # 设置进程数 for i in range(5): p.apply_async(foo, args=(i,)) # 设置每个进程要执行的函数和参数 print 'Waiting for all subprocesses done...' p.close() p.join() print 'All subprocesses done.'
在上面的代码中,Pool 用于生成进程池,对 Pool 对象调用 apply_async 方法可以使每个进程异步执行任务,也就说不用等上一个任务执行完才执行下一个任务,
close 方法用于关闭进程池,确保没有新的进程加入,
join 方法会等待所有子进程执行完毕。
看看执行结果:
Parent process 7170.Run task 1 (pid:10320)...Run task 0 (pid:10319)...Run task 3 (pid:10322)...Run task 2 (pid:10321)...Waiting for all subprocesses done...Task 1 result is: 1Task 0 result is: 0Run task 4 (pid:10320)...Task 3 result is: 9Task 2 result is: 4Task 4 result is: 16All subprocesses done.
4.进程间通信
进程间的通信可以通过管道(Pipe),队列(Queue)等多种方式来实现。
Python 的 multiprocessing 模块封装了底层的实现机制,让我们可以很容易地实现进程间的通信。
下面以队列(Queue)为例,在父进程中创建两个子进程,
一个往队列写数据,一个从对列读数据
小结
- 由于每个进程都有各自的内存空间,数据栈等,所以只能使用进程间通讯(Inter-Process Communication, IPC),而不能直接共享信息。
- Python 的
multiprocessing 模块封装了底层的实现机制,让我们可以更简单地编写多进程程序。