|
1 | 1 | # 进程 |
2 | 2 |
|
| 3 | +**进程(process)是正在运行的程序的实例,但一个程序可能会产生多个进程**。比如,打开 Chrome 浏览器程序,它可能会产生多个进程,主程序需要一个进程,一个网页标签需要一个进程,一个插件也需要一个进程,等等。 |
| 4 | + |
| 5 | +每个进程都有自己的地址空间,内存,数据栈以及其他记录其运行状态的辅助数据,不同的进程只能使用消息队列、共享内存等进程间通讯(IPC)方法进行通信,而不能直接共享信息。 |
| 6 | + |
| 7 | +# fork() |
| 8 | + |
| 9 | +在介绍 Python 的进程编程之前,让我们先看看 Unix/Linux 中的 `fork` 函数。在 Unix/Linux 系统中,`fork` 函数被用于创建进程。这个函数很特殊,对于普通的函数,调用它一次,返回一次,但是调用 `fork` 一次,它返回两次。事实上,`fork` 函数创建了新的进程,我们把它称为子进程,子进程几乎是当前进程(即父进程)的一个拷贝:它会复制父进程的代码段,堆栈段和数据段。 |
| 10 | + |
| 11 | +对于父进程,`fork` 函数返回了子进程的进程号 pid,对于子进程,`fork` 函数则返回 `0`,这也是 `fork` 函数返回两次的原因,根据返回值,我们可以判断进程是父进程还是子进程。 |
| 12 | + |
| 13 | +下面我们看一段 C 代码,它展示了 fork 的基本使用: |
| 14 | + |
| 15 | +```c |
| 16 | +#include <unistd.h> |
| 17 | +#include <stdio.h> |
| 18 | + |
| 19 | +int main(int argc, char const *argv[]) |
| 20 | +{ |
| 21 | + int pid; |
| 22 | + pid = fork(); // 使用 fork 函数 |
| 23 | + |
| 24 | + if (pid < 0) { |
| 25 | + printf("Fail to create process\n"); |
| 26 | + } |
| 27 | + else if (pid == 0) { |
| 28 | + printf("I am child process (%d) and my parent is (%d)\n", getpid(), getppid()); |
| 29 | + } |
| 30 | + else { |
| 31 | + printf("I (%d) just created a child process (%d)\n", getpid(), pid); |
| 32 | + } |
| 33 | + return 0; |
| 34 | +} |
| 35 | +``` |
| 36 | +
|
| 37 | +其中,`getpid` 用于获取当前进程号,`getppid` 用于获取父进程号。 |
| 38 | +
|
| 39 | +事实上,Python 的 os 模块包含了普遍的操作系统功能,该模块也提供了 `fork` 函数,把上面的代码改成用 Python 来实现,如下: |
| 40 | +
|
| 41 | +```python |
| 42 | +import os |
| 43 | +
|
| 44 | +pid = os.fork() |
| 45 | +
|
| 46 | +if pid < 0: |
| 47 | + print 'Fail to create process' |
| 48 | +elif pid == 0: |
| 49 | + print 'I am child process (%s) and my parent is (%s).' % (os.getpid(), os.getppid()) |
| 50 | +else: |
| 51 | + print 'I (%s) just created a child process (%s).' % (os.getpid(), pid) |
| 52 | +``` |
| 53 | + |
| 54 | +运行上面的代码,产生如下输出: |
| 55 | + |
| 56 | +``` |
| 57 | +I (86645) just created a child process (86646). |
| 58 | +I am child process (86646) and my parent is (86645). |
| 59 | +``` |
| 60 | + |
| 61 | +需要注意的是,虽然子进程复制了父进程的代码段和数据段等,但是一旦子进程开始运行,子进程和父进程就是相互独立的,它们之间不再共享任何数据。 |
| 62 | + |
| 63 | +# 多进程 |
| 64 | + |
| 65 | +Python 提供了一个 [multiprocessing][mp] 模块,利用它,我们可以来编写跨平台的多进程程序。 |
| 66 | + |
| 67 | +我们先来看一个简单的例子,该例子演示了在主进程中启动一个子进程,并等待其结束,代码如下: |
| 68 | + |
| 69 | +```python |
| 70 | +import os |
| 71 | +from multiprocessing import Process |
| 72 | + |
| 73 | +# 子进程要执行的代码 |
| 74 | +def child_proc(name): |
| 75 | + print 'Run child process %s (%s)...' % (name, os.getpid()) |
| 76 | + |
| 77 | +if __name__ == '__main__': |
| 78 | + print 'Parent process %s.' % os.getpid() |
| 79 | + p = Process(target=child_proc, args=('test',)) |
| 80 | + print 'Process will start.' |
| 81 | + p.start() |
| 82 | + p.join() |
| 83 | + print 'Process end.' |
| 84 | +``` |
| 85 | + |
| 86 | +在上面的代码中,我们从 multiprocessing 模块引入了 Process,Process 是一个用于创建进程对象的类,其中,target 指定了进程要执行的函数,args 指定了参数。在创建了进程实例 p 之后,我们调用 start 方法开始执行该子进程,接着,我们又调用了 join 方法,该方法用于阻塞子进程以外的所有进程(这里指父进程),当子进程执行完毕后,父进程才会继续执行,它通常用于进程间的同步。 |
| 87 | + |
| 88 | +可以看到,用上面这种方式来创建进程比直接使用 `fork` 更简单易懂。现在,让我们看下输出结果: |
| 89 | + |
| 90 | +``` |
| 91 | +Parent process 7170. |
| 92 | +Process will start. |
| 93 | +Run child process test (10075)... |
| 94 | +Process end. |
| 95 | +``` |
| 96 | + |
| 97 | +## 使用进程池创建多个进程 |
| 98 | + |
| 99 | +在上面,我们只是创建了一个进程,如果要创建多个进程呢?Python 提供了**进程池**的方式,让我们批量创建子进程,让我们看一个简单的示例: |
| 100 | + |
| 101 | +```python |
| 102 | +import os, time |
| 103 | +from multiprocessing import Pool |
| 104 | + |
| 105 | +def foo(x): |
| 106 | + print 'Run task %s (pid:%s)...' % (x, os.getpid()) |
| 107 | + time.sleep(2) |
| 108 | + print 'Task %s result is: %s' % (x, x * x) |
| 109 | + |
| 110 | +if __name__ == '__main__': |
| 111 | + print 'Parent process %s.' % os.getpid() |
| 112 | + p = Pool(4) # 设置进程数 |
| 113 | + for i in range(5): |
| 114 | + p.apply_async(foo, args=(i,)) # 设置每个进程要执行的函数和参数 |
| 115 | + print 'Waiting for all subprocesses done...' |
| 116 | + p.close() |
| 117 | + p.join() |
| 118 | + print 'All subprocesses done.' |
| 119 | +``` |
| 120 | + |
| 121 | +在上面的代码中,Pool 用于生成进程池,对 Pool 对象调用 apply_async 方法可以使每个进程异步执行任务,也就说不用等上一个任务执行完才执行下一个任务,close 方法用于关闭进程池,确保没有新的进程加入,join 方法会等待所有子进程执行完毕。 |
| 122 | + |
| 123 | +看看执行结果: |
| 124 | + |
| 125 | +```python |
| 126 | +Parent process 7170. |
| 127 | +Run task 1 (pid:10320)... |
| 128 | +Run task 0 (pid:10319)... |
| 129 | +Run task 3 (pid:10322)... |
| 130 | +Run task 2 (pid:10321)... |
| 131 | +Waiting for all subprocesses done... |
| 132 | +Task 1 result is: 1 |
| 133 | +Task 0 result is: 0 |
| 134 | +Run task 4 (pid:10320)... |
| 135 | +Task 3 result is: 9 |
| 136 | +Task 2 result is: 4 |
| 137 | +Task 4 result is: 16 |
| 138 | +All subprocesses done. |
| 139 | +``` |
| 140 | + |
| 141 | +# 进程间通信 |
| 142 | + |
| 143 | +进程间的通信可以通过管道(Pipe),队列(Queue)等多种方式来实现。Python 的 multiprocessing 模块封装了底层的实现机制,让我们可以很容器地实现进程间的通信。 |
| 144 | + |
| 145 | +下面以队列(Queue)为例,在父进程中创建两个子进程,一个往队列写数据,一个从对列读数据,代码如下: |
| 146 | + |
| 147 | +```python |
| 148 | +# -*- coding: utf-8 -*- |
| 149 | + |
| 150 | +from multiprocessing import Process, Queue |
| 151 | + |
| 152 | +# 向队列中写入数据 |
| 153 | +def write_task(q): |
| 154 | + try: |
| 155 | + n = 1 |
| 156 | + while n < 5: |
| 157 | + print "write, %d" % n |
| 158 | + q.put(n) |
| 159 | + time.sleep(1) |
| 160 | + n += 1 |
| 161 | + except BaseException: |
| 162 | + print "write_task error" |
| 163 | + finally: |
| 164 | + print "write_task end" |
| 165 | + |
| 166 | +# 从队列读取数据 |
| 167 | +def read_task(q): |
| 168 | + try: |
| 169 | + n = 1 |
| 170 | + while n < 5: |
| 171 | + print "read, %d" % q.get() |
| 172 | + time.sleep(1) |
| 173 | + n += 1 |
| 174 | + except BaseException: |
| 175 | + print "read_task error" |
| 176 | + finally: |
| 177 | + print "read_task end" |
| 178 | + |
| 179 | +if __name__ == "__main__": |
| 180 | + q = Queue() # 父进程创建Queue,并传给各个子进程 |
| 181 | + |
| 182 | + pw = Process(target=write_task, args=(q,)) |
| 183 | + pr = Process(target=read_task, args=(q,)) |
| 184 | + |
| 185 | + pw.start() # 启动子进程 pw,写入 |
| 186 | + pr.start() # 启动子进程 pr,读取 |
| 187 | + pw.join() # 等待 pw 结束 |
| 188 | + pr.join() # 等待 pr 结束 |
| 189 | + print "DONE" |
| 190 | +``` |
| 191 | + |
| 192 | +执行结果如下: |
| 193 | + |
| 194 | +```python |
| 195 | +write, 1 |
| 196 | +read, 1 |
| 197 | +write, 2 |
| 198 | +read, 2 |
| 199 | +write, 3 |
| 200 | +read, 3 |
| 201 | +write, 4 |
| 202 | +read, 4 |
| 203 | +write_task end |
| 204 | +read_task end |
| 205 | +DONE |
| 206 | +``` |
| 207 | + |
| 208 | +# 小结 |
| 209 | + |
| 210 | +- 进程是正在运行的程序的实例。 |
| 211 | +- 由于每个进程都有各自的内存空间,数据栈等,所以只能使用进程间通讯(Inter-Process Communication, IPC),而不能直接共享信息。 |
| 212 | +- Python 的 multiprocessing 模块封装了底层的实现机制,让我们可以更简单地编写多进程程序。 |
| 213 | + |
| 214 | +# 参考资料 |
| 215 | + |
| 216 | +- [多进程 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868323401155ceb3db1e2044f80b974b469eb06cb43000) |
| 217 | +- [Linux下Fork与Exec使用 - hicjiajia - 博客园](http://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940154.html) |
| 218 | +- [Python 中的进程、线程、协程、同步、异步、回调 - 七牛云存储 - SegmentFault](https://segmentfault.com/a/1190000001813992) |
| 219 | +- [编程中的进程、线程、协程、同步、异步、回调 · 浮生半日闲](https://wangdashuaihenshuai.github.io/2015/10/17/%E7%BC%96%E7%A8%8B%E4%B8%AD%E7%9A%84%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E3%80%81%E5%8D%8F%E7%A8%8B%E3%80%81%E5%90%8C%E6%AD%A5%E3%80%81%E5%BC%82%E6%AD%A5%E3%80%81%E5%9B%9E%E8%B0%83/) |
| 220 | +- [python中多进程以及多线程编程的总结 - Codefly](http://sunms.codefly.top/2016/11/05/python%E4%B8%AD%E5%A4%9A%E8%BF%9B%E7%A8%8B%E4%BB%A5%E5%8F%8A%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B%E7%9A%84%E6%80%BB%E7%BB%93/) |
| 221 | +- [multithreading - Python multiprocessing.Pool: when to use apply, apply_async or map? - Stack Overflow](http://stackoverflow.com/questions/8533318/python-multiprocessing-pool-when-to-use-apply-apply-async-or-map) |
| 222 | + |
| 223 | + |
| 224 | + |
| 225 | +[mp]: https://docs.python.org/2/library/multiprocessing.html |
| 226 | + |
| 227 | + |
0 commit comments