ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] # 进程状态 ![](https://box.kancloud.cn/12d1215f70c332d1bd4839cd0a9967ed_662x216.png) * 就绪状态: 运行的条件都已经满足,正在等待cpu执行 * 执行态: cpu正在执行其功能 * 等待态: 等待某些条件满足,例如一个程序sleep了,此时就处于等待态 # fork方式 Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。 子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID ~~~ import os # 表示父进程开始了 print('Process (%s) start...' % os.getpid()) # 只能unix和linux和mac pid = os.fork() if pid == 0: # 子进程 print('我是子进程,进程id是 (%s) , 父进程的id是 (%s)' % (os.getpid(), os.getppid())) else: # 父进程 print('我是父进程,进程id是 (%s) , 我创建的子进程id是 (%s) ' % (os.getpid(), pid)) ~~~ 输出 ~~~ Process (32454) start... 我是父进程,进程id是 (32454) , 我创建的子进程id是 (32458) 我是子进程,进程id是 (32458) , 父进程的id是 (32454) ~~~ 由于Windows没有fork调用,上面的代码在Windows上无法运行 # multiprocessing multiprocessing模块就是跨平台版本的多进程模块 multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束: ~~~ import multiprocessing import os # 子进程要执行的代码 def run_proc(name): print('子进程的参数name是 (%s) , id是 (%s) ' % (name, os.getpid())) if __name__ == '__main__': print('父进程的id是 (%s)' % os.getpid()) p = multiprocessing.Process(target=run_proc, args=('test', )) print('子进程将要开始了...') p.start() p.join() print('子进程结束...') ~~~ 输出 ~~~ 父进程的id是 (32564) 子进程将要开始了... 子进程的参数name是 (test) , id是 (32568) 子进程结束... ~~~ 创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。 join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步 # Pool进程池 如果要启动大量的子进程,可以用进程池的方式批量创建子进程 ~~~ import multiprocessing import os, time, random def long_time_task(name): print('参数name:(%s),子进程id是(%s)' % (name, os.getpid())) start = time.time() time.sleep(random.random() * 3) end = time.time() print('参数name:(%s),运行了 %0.2f 秒' % (name, (end - start))) if __name__ == '__main__': print('父进程的id是 (%s)' % os.getpid()) # 创建4个子进程 p = multiprocessing.Pool(4) # 循环5次,投递5个任务给这些子进程 for i in range(5): p.apply_async(long_time_task, args=(i, )) print('等待所有进程结束') # 关闭进程池,关闭后不再接收新的请求 p.close() # 等待po中所有子进程执行完成,必须放在close之后 p.join() print('所有进程结束') ~~~ 输出 ~~~ 父进程的id是 (35744) 等待所有进程结束 参数name:(0),子进程id是(35749) 参数name:(1),子进程id是(35750) 参数name:(2),子进程id是(35751) 参数name:(3),子进程id是(35752) 参数name:(2),运行了 1.57 秒 参数name:(4),子进程id是(35751) 参数name:(3),运行了 2.22 秒 参数name:(0),运行了 2.76 秒 参数name:(1),运行了 2.94 秒 参数name:(4),运行了 2.54 秒 所有进程结束 ~~~ 代码解读: 对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。 请注意输出的结果,task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成 # 控制子进程输入和输出 很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。 subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出 下面的例子演示了如何在Python代码中运行命令`nslookup www.python.org`(查询域名的DNS解析),这和命令行直接运行的效果是一样的 ~~~ import subprocess print('$ nslookup www.python.org') r = subprocess.call(['nslookup', 'www.python.org']) print('结束的状态码:', r) ~~~ 输出 ~~~ $ nslookup www.python.org Server: 192.168.3.1 Address: 192.168.3.1#53 Non-authoritative answer: www.python.org canonical name = dualstack.python.map.fastly.net. Name: dualstack.python.map.fastly.net Address: 151.101.108.223 结束的状态码: 0 ~~~ 如果子进程还需要输入,则可以通过communicate()方法输入 ~~~ import subprocess print('$ nslookup') p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, err = p.communicate(b'set q=mx\npython.org\nexit\n') print(output.decode('utf-8')) print('Exit code:', p.returncode) ~~~ 上面的代码相当于在命令行执行命令nslookup,然后手动输入 ~~~ set q=mx python.org exit ~~~ 运行结果如下: ~~~ $ nslookup Server: 192.168.19.4 Address: 192.168.19.4#53 Non-authoritative answer: python.org mail exchanger = 50 mail.python.org. Authoritative answers can be found from: mail.python.org internet address = 82.94.164.166 mail.python.org has AAAA address 2001:888:2000:d::a6 Exit code: 0 ~~~ # 进程间通信 Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。 **常用方法** ~~~ # 创建队列,可以指定容量 q = multiprocessing.Queue() # 队列是不是满的 q.full() # 获取数据 q.get() # 队列是不是空 q.empty() # 写入数据 q.put(xx) ~~~ 我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据 ~~~ import multiprocessing import os, time, random # 写数据进程执行的代码 def write(q): print('进程id (%s) 正在写入' % os.getpid()) for value in ['A', 'B', 'C']: print('把(%s)放到队列...' % value) q.put(value) time.sleep(random.random()) # 读数据进程执行的代码 def read(q): print('进程id (%s) 正在读取' % os.getpid()) while True: value = q.get(True) print('获取(%s)从队列中' % value) if __name__ == '__main__': # 父进程创建queue,并传给各个子进程 q = multiprocessing.Queue() pw = multiprocessing.Process(target=write, args=(q, )) pr = multiprocessing.Process(target=read, args=(q, )) # 启动子进程pw,写入: pw.start() # 启动子进程pr,写入: pr.start() # 等待pw结束 pw.join() # pr进程里是死循环,无法等待其结束,只能强行终止 pr.terminate() ~~~ 输出 ~~~ 进程id (36109) 正在写入 把(A)放到队列... 进程id (36110) 正在读取 获取(A)从队列中 把(B)放到队列... 获取(B)从队列中 把(C)放到队列... 获取(C)从队列中 ~~~ 在Unix/Linux下,multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节。由于Windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,所有,如果multiprocessing在Windows下调用失败了,要先考虑是不是pickle失败了