Python札记50_进程和线程
现在的操作系统不管是
Mac OS X ,UNIX,Linux
或者Windows
都是支持“多任务”的操作系统。多任务指的是同时运行多个任务,比如此时笔者正在用浏览器上网,也在输入文字,同时
-
CPU
中执行代码是按照顺序执行的 - 单核
CPU
:操作系统让每个任务交替执行;CPU
运行速度快,用户感觉就像是同时运行 - 多核
CPU
:真正意义上同时执行多个任务。任务数量远多于CPU
核数,系统让每个任务轮流到核心上执行
在此感谢廖雪峰老师的教程:进程和线程
进程process
对于操作系统,一个任务就是一个进程。有些进程的内部不是只做一件事情,比如开启word:打字、拼写检查、打印等同时进行。进程内部同时进行多个“子任务”,进程内部的“子任务”就是线程。进程特点:
- 一个进程至少有一个线程
- 一个进程中可以有多个线程,多个线程同时进行
线程Thread
一个进程内部有多个“子任务”,这些子任务就是线程。
- 多线程的执行由操作系统在多个线程之间快速切换
- 真正的多线程由多核CPU才能实现
- Python程序就是单任务的进程,也就是只有一个线程。
多个任务同时执行怎么实现?
1.多进程模式
2.多线程模式
3.多进程+多线程模式
多进程multiprocessing
关于fork()
Linux
中提供了fork()
调用。特殊之处在于:普通函数被调用只返回一次;调用一次,返回两次。操作系统将当前进程(父进程)复制一份(子进程),分别在父进程和子进程
内进行返回。
- 子进程返回的是0
- 父进程返回的是子进程的
ID
- 一个父进程可以
fork
多个子进程,每个父进程记下每个子进程的ID
- 子进程调用
getppid()
就可以得到父进程的ID
- os.getpid():获取子进程id
- os.getppid():获取父进程id(parent pid)
如何在Python中创建子进程?
Python中的os模块封装了常用的系统调用,在程序中轻松地调用创建子进程:
import os # 导入模块;os模块实现系统进程的封装
print("Process {} start...".format(os.getpid())) # 查询父进程
pid = os.fork() # 调用函数fork
if pid == 0: # 子进程的pid是0
print("I am child {0} and my parent is {1}".format(os.getpid(), os.getppid()))
else:
print("I {0} just started a child process{1}".format(os.getpid(), pid))
fork在windows中没有,只有在Linux或者Mac系统中存在。
image.png
总结:
- 有了fork调用,进程在接收到新的任务会复制出新的子进程来处理任务,常见的apache服务器就是通过这样的方式,fork出子进程来处理子进程。
-
fork()
调用执行两次;fork()
只能在Linux
或Unix
上执行 -
Windows
上 无法直接执行,只能调用multiprocessing
模块
根据上面的描述,
Windows
系统是不提供fork
功能的,如何在Windows
上怎么实现多进程?使用multiprocessing
。multiprocessing
模块是跨平台版本的多进程模块。模块中提供了一个Process
类来代表一个进程对象。
1 import os
2 from multiprocessing import Process
3
4 # 子进程执行的代码
5 def run_proc(name):
6 print(“Run child process {0} {1}”.format(name, os.getpid()))
7
8 if __name__ == "__main__":
9 print("Parent process {0}".format(os.getppid())) # 打印当前父进程
10 p = Process(target=run_proc, args=('test',)) # 通过类创建实例p
11 print("Child process will start") # 提示:子进程开启
12 p.start() # 调用实例的开启方法
13 p.join() # 用于进程间的同步
14 print("Child process end")
image.png
- 创建子进程需要传入一个执行函数和函数的参数
- 创建Process实例,利用
start()方法启动
-jion()
方法是子进程结束之后再继续往下运行,通常用于进程间的同步
。
批量创建子进程
如果要启动大量的子进程,使用进程池Pool
的方法来实现
1 from multiprocessing import Pool # 导入进程池的类
2 import os,time,random # 导入三种模块
3
4 def long_time_task(name): # 定义任务执行函数
5 print("Run task {} {}".format(name, os.getpid())) # 获取当前子进程信息
6 start = time.time() # 任务执行的开始时间,时间戳形式
7 time.sleep(random.random() * 3) # 随机sleep时间
8 end = time.time() # 任务结束时间
9 print("Task {} runs {:.2f} seconds".format(name, (end - start)))
10
11 if __name__ == "__main__":
12 print("Parent process {}".format(os.getppid())) # 获取当前父进程信息
13 p = Pool(4) # 通过类创建实例p
14 for i in range(5):
15 p.apply_async(long_time_task, args=(i, )) # 通过实例的apply_async()方法创建进程;一个函数,一个是函数执行的参数
16 print("Waiting for all subprocess done...")
17 p.close() # close必须在join的前面
18 p.join()
19 print("All subprocess done")
执行结果:
Parent process 27501
Waiting for all subprocess done...
Run task 0 18868
Run task 1 18869
Run task 2 18870
Run task 3 18871
Task 2 runs 0.86 seconds
Run task 4 18870
Task 0 runs 0.91 seconds
Task 1 runs 1.25 seconds
Task 4 runs 1.08 seconds
Task 3 runs 2.99 seconds
All subprocess done
解释
- join()方法会等待所有的子进程执行完毕
- close()方法必须要在join()之前;close之后就不能调用新的Process
- Pool的大小默认是CPU的核数
子进程
有时候子进程并不是自身,而是一个外部的进程。可以通过subprocess
模块来控制子进程的输入和输出。
例如下面的代码等同于运行命令'nslookup www.python.org'
import subprocess # 导入模块
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org']) # 调用模块的call方法
print('Exit code:', r)
image.png
通过communicate()
控制子进程继续输入
进程间通信
各种进程间是需要通信的。Python
的multiprocessing
模块包装了底层的机制,提供了Queue、Pipes
等多种方式来交换数据。
以Queue为例,在父进程中创建两个子进程,一个往Queue中读数据,一个写数据: