Python开发【笔记】:进程
序言
进程与线程概述:
很多同学都听说过,现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。
什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。
我们前面编写的所有的Python程序,都是执行单任务的进程,也就是只有一个线程。如果我们要同时执行多个任务怎么办?
有两种解决方案:
一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。
还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务。
当然还有第三种方法,就是启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模型更复杂,实际很少采用。
总结一下就是,多任务的实现有3种方式:
- 多进程模式;
- 多线程模式;
- 多进程+多线程模式。
同时执行多个任务通常各个任务之间并不是没有关联的,而是需要相互通信和协调,有时,任务1必须暂停等待任务2完成后才能继续执行,有时,任务3和任务4又不能同时执行,所以,多进程和多线程的程序的复杂度要远远高于我们前面写的单进程单线程的程序。
因为复杂度高,调试困难,所以,不是迫不得已,我们也不想编写多任务。但是,有很多时候,没有多任务还真不行。想想在电脑上看电影,就必须由一个线程播放视频,另一个线程播放音频,否则,单线程实现的话就只能先把视频播放完再播放音频,或者先把音频播放完再播放视频,这显然是不行的。
Python既支持多进程,又支持多线程,我们会讨论如何编写这两种多任务程序。
进程
初识:
要让Python程序实现多进程(multiprocessing),我们先了解操作系统的相关知识。Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:
import os
print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
# Process (44587) start...
# I (44587) just created a child process (44588).
# I am child process (44588) and my parent is 44587.
由于Windows没有fork调用,上面的代码在Windows上无法运行。由于Mac系统是基于BSD(Unix的一种)内核,所以,在Mac下运行是没有问题的,推荐大家用Mac学Python!有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。
multiprocessing模块:
如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:
import os
import time # 子进程要执行的代码
def run_proc(name):
time.sleep(1)
print('Run child process %s (%s)...' % (name, os.getpid())) if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',)) # args里面为何要用,隔开?
p.start() # 子进程启动,不加这个子进程不执行
p.join() # 等待子进程p的执行完毕后再向下执行,不加此项,主程序执行完毕,子进程依然会继续执行不受影响
print('Child process end.'), # Parent process 8428.
# Run child process test (9392)...
# Child process end.
Process实例化时执行self._args = tuple(args)操作,如果不用,隔开生成的slef._args就是一个个字母了,传入两个参数以上是就不用加,号了,如下:
def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
*, daemon=None):
assert group is None, 'group argument must be None for now'
count = next(_process_counter)
self._identity = _current_process._identity + (count,)
self._config = _current_process._config.copy()
self._parent_pid = os.getpid()
self._popen = None
self._target = target
self._args = tuple(args)
a =('ers')
b = tuple(a)
print(b)
# ('e', 'r', 's')
a1 =('ers','gte')
b1 = tuple(a1)
print(b1)
# ('ers', 'gte')
Process代码
Pool进程池:
如果要启动大量的子进程,可以用进程池的方式批量创建子进程:
from multiprocessing import Pool,cpu_count
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))) def Bar(arg):
print('-->exec done:',arg,os.getpid()) if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(cpu_count()) # 获取当前cpu核数,多核cpu的情况下多进程才能实现真正的并发
for i in range(5):
# p.apply_async(func=long_time_task, args=(i,), callback=Bar) #callback回调 执行完func后再执行callback 用主程序执行
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join() # !等待进程池执行完毕,不然主进程执行完毕后,进程池直接关闭
print('All subprocesses done.') # Parent process 4492.
# Waiting for all subprocesses done...
# Run task 0 (3108)...
# Run task 1 (7936)...
# Run task 2 (11236)...
# Run task 3 (8284)...
# Task 2 runs 0.86 seconds.
# Run task 4 (11236)...
# Task 0 runs 1.34 seconds.
# Task 1 runs 1.49 seconds.
# Task 3 runs 2.62 seconds.
# Task 4 runs 1.90 seconds.
# All subprocesses done.
重点:另进程池里的进程执行完毕后,进程关闭自动销毁,不再占用内存,同理,非进程池创建的子进程,执行完毕后也是自动销毁,具体测试如下:
from multiprocessing import Pool,cpu_count
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))) def count_process():
import psutil
pids = psutil.pids()
process_name = []
for pid in pids:
p = psutil.Process(pid)
process_name.append(p.name()) # 获取进程名
# process_name.append(p.num_threads()) # 获取进程的线程数
# print process_name
print len(process_name) 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...')
count_process() # 进程池开始时进程数(包含系统其他应用进程)
p.close()
p.join()
count_process() # 进程池关闭时进程数
print('All subprocesses done.') # Parent process 8860.
# Waiting for all subprocesses done...
# Run task 0 (2156)...
# Run task 1 (1992)...
# Run task 2 (10680)...
# Run task 3 (11216)...
# 109 开始
# Task 2 runs 0.93 seconds.
# Run task 4 (10680)...
# Task 1 runs 1.71 seconds.
# Task 3 runs 2.01 seconds.
# Task 0 runs 2.31 seconds.
# Task 4 runs 2.79 seconds.
# 105 结束
# All subprocesses done.
进程池子进程执行完毕后销毁
代码解读:
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
请注意输出的结果,task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:
p = Pool(5)
就可以同时跑5个进程。
由于Pool的默认大小是CPU的核数,如果你不幸拥有8核CPU,你要提交至少9个子进程才能看到上面的等待效果。
进程间通信:
Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
from multiprocessing import Process, Queue
import os, time, random # 写数据进程执行的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random()) # 读数据进程执行的代码:
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value) if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate() # 强制关闭子进程 # Process to write: 9472
# Put A to queue...
# Process to read: 3948
# Get A from queue.
# Put B to queue...
# Get B from queue.
# Put C to queue...
# Get C from queue.
在Unix/Linux下,multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节。由于Windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,所有,如果multiprocessing在Windows下调用失败了,要先考虑是不是pickle失败了。
进程间共享数据:
有时候我们不仅仅需要进程间数据传输,也需要多进程间进行数据共享,即可以使用同一全局变量;如:为何下面程序的列表输出为空?
from multiprocessing import Process, Manager
import os # manager = Manager()
vip_list = []
#vip_list = manager.list() def testFunc(cc):
vip_list.append(cc)
print 'process id:', os.getpid() if __name__ == '__main__':
threads = [] for ll in range(10):
t = Process(target=testFunc, args=(ll,))
t.daemon = True
threads.append(t) for i in range(len(threads)):
threads[i].start() for j in range(len(threads)):
threads[j].join() print "------------------------"
print 'process id:', os.getpid()
print vip_list # process id: 9436
# process id: 11120
# process id: 10636
# process id: 1380
# process id: 10976
# process id: 10708
# process id: 2524
# process id: 9392
# process id: 10060
# process id: 8516
# ------------------------
# process id: 9836
# []
如果你了解 python 的多线程模型,GIL 问题,然后了解多线程、多进程原理,上述问题不难回答,不过如果你不知道也没关系,跑一下上面的代码你就知道是什么问题了。因为进程间内存是独立的
正如上面提到的,在进行并发编程时,最好尽可能避免使用共享状态。在使用多个进程时尤其如此。但是,如果您确实需要使用一些共享数据,那么多处理提供了两种方法。
① 共享内存:
数据可以使用值或数组存储在共享内存映射中。例如,下面的代码:
from multiprocessing import Process, Value, Array def f(n, a):
n.value = 3.1415927
for i in range(len(a)):
a[i] = -a[i] if __name__ == '__main__':
num = Value('d', 0.0)
arr = Array('i', range(10)) p = Process(target=f, args=(num, arr))
p.start()
p.join() print num.value
print arr[:] # 3.1415927
# [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
在创建num和arr时使用的“i”和“i”参数是数组模块使用的类型的类型:“表示双精度浮点数”,“i”表示一个已签名的整数。这些共享对象将是进程和线程安全的。为了更灵活地使用共享内存,您可以使用多处理。sharedctypes模块支持创建从共享内存中分配的任意类型的ctypes对象。
② 服务进程:
manager()返回的manager对象控制一个保存Python对象的服务器进程,并允许其他进程使用代理来操作它们。manager()返回的管理器将支持类型列list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Queue, Value and Array。如下:
from multiprocessing import Process, Manager def f(d, l):
d[1] = '1'
d['2'] = 2
d[0.25] = None
l.reverse() if __name__ == '__main__':
manager = Manager() d = manager.dict()
l = manager.list(range(10)) p = Process(target=f, args=(d, l))
p.start()
p.join() print d
print l # {0.25: None, 1: '1', '2': 2}
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
服务器进程管理器比使用共享内存对象更灵活,因为它们可以用来支持任意对象类型。另外,单个管理器可以通过网络上不同计算机上的进程共享。但是,它们比使用共享内存要慢。
更多-》》点击
小结
在Unix/Linux下,可以使用fork()调用实现多进程。
要实现跨平台的多进程,可以使用multiprocessing模块。
进程间通信是通过Queue(多进程间)、Pipes(两个进程间)等实现的。
补充小知识点-》父进程开辟子进程,子进程开辟子子进程,如果把子进程杀掉,子子进程会被杀死吗?
import time
from multiprocessing import Process
import os def count_process():
import psutil
pids = psutil.pids()
print len(pids) def test3():
count_process()
for i in range(10):
print "test3 %s"%os.getpid()
time.sleep(0.5) def test1():
print "test1 %s"%os.getpid()
p2 = Process(target=test3, name="protest2")
p2.start()
p2.join() if __name__ == '__main__':
count_process()
p1 = Process(target=test1, name="protest1")
p1.start()
time.sleep(2)
p1.terminate()
time.sleep(2)
count_process()
for i in range(10):
print(i)
time.sleep(1) #
# test1 9500
#
# test3 3964
# test3 3964
# test3 3964
# test3 3964
# test3 3964
# test3 3964
# test3 3964
# test3 3964
#
#
# test3 3964
# test3 3964
#
#
#
#
#
#
#
#
#
子子进程的心路历程
Python开发【笔记】:进程的更多相关文章
- python开发笔记-通过xml快捷获取数据
今天在做下python开发笔记之如何通过xml快捷获取数据,下面以调取nltk语料库为例: import nltk nltk.download() showing info https://raw.g ...
- supervisor python开发的进程管理工具
Supervisor (http://supervisord.org) 是一个用 Python 写的进程管理工具,可以很方便的用来启动.重启.关闭进程(不仅仅是 Python 进程).除了对单个进程的 ...
- python开发笔记-python调用webservice接口
环境描述: 操作系统版本: root@9deba54adab7:/# uname -a Linux 9deba54adab7 --generic #-Ubuntu SMP Thu Dec :: UTC ...
- python学习笔记-进程线程
1.什么是进程(process)? 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述 ...
- Python自学笔记-进程,线程(Mr serven)
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了 ...
- python开发笔记-Python3.7+Django2.2 Docker镜像搭建
目标镜像环境介绍: 操作系统:ubuntu16.04 python版本:python 3.7.4 django版本:2.2 操作步骤: 1. 本地安装docker环境(略)2. 拉取ubunut指定 ...
- python开发笔记之zip()函数用法详解
今天分享一篇关于python下的zip()函数用法. zip()是Python的一个内建函数,它接受一系列可迭代的对象作为参数,将对象中对应的元素按顺序组合成一个tuple,每个tuple中包含的是原 ...
- python开发: linux进程占用物理内存
#!/usr/bin/env python #-*- coding:utf-8 -*- ''' 统计linux进程占用的物理内存 ''' import os import sys import sub ...
- Python开发笔记之正则表达式的使用
查找正则表达式 import re re_txt = re.compile(r'(\d)*.txt') m = re_txt.search(src) if not m == None: m.group ...
- python开发笔记-类
类的基本概念: 问题空间:问题空间是问题解决者对一个问题所达到的全部认识状态,它是由问题解决者利用问题所包含的信息和已贮存的信息主动的地构成的. 初始状态:一开始时的不完全的信息或令人不满意的状况: ...
随机推荐
- linux 访问远程务器代码
比如用SSH 访问远程 登陆名为hadoop 的IP为192.168.1.35的主机,则用ssh hadoop@192.168.1.35,然后依据提示输入密码即可.
- VS 清除编译产生的临时文件、文件夹
VS编译过程中会产生一些临时文件,通过以下脚本可清除 @echo off for /r %%i in (*.sdf,*.ncb,*.suo,*.exp,*.user,*.aps,*.idb,*.dep ...
- windows cmd中查看某个命令所在的路径
需求描述: 之前用linux环境下的which命令就能看到某个命令的绝对路径, 然后想在windows下的cmd中是否也能够查看到命令的绝对路径呢 操作过程: 1.windows环境下,通过where ...
- makefile--嵌套执行(四)
原创博文,转载请标明出处--周学伟http://www.cnblogs.com/zxouxuewei/ 在大一些的项目里面,所有源代码不会只放在同一个目录,一般各个功能模块的源代码都是分开的,各自放在 ...
- visual studio 2017使用NHibernate4.0连接oracle11g数据库
之前一直是公司用NHibernate2.1来做项目,连接oracle 10g的数据库,配置NHibernate的东西都是以前的同事做好了的,也怪自己太懒了,没尝试过配置这个东西,虽然一直在使用NHib ...
- JsonConvert.DeserializeAnonymousType
string JsApiTicketString = string.Empty; using (StreamReader reader = new StreamReader(response.GetR ...
- day21<IO流+&FIle递归>
IO流(字符流FileReader) IO流(字符流FileWriter) IO流(字符流的拷贝) IO流(什么情况下使用字符流) IO流(字符流是否可以拷贝非纯文本的文件) IO流(自定义字符数组的 ...
- docker学习之-什么是docker
docker是一个用来装应用的容器,就想杯子可以装水,笔筒可以装笔,书包可以放书一样,可以把网站放到docker里,可以把任何应用放到docker里.
- C#中的抽象类与重写
今天的我们学习了好多,最初上午学习了文件流的方法,老师告诉我们是选修,可能以后不怎么用吧,但是还是想学下,似乎用个小程序读写文件很快地节奏,所以有点小兴趣学习,明天我再看看啦!今天之后学习了多态,继承 ...
- PyQt4日历部件QXalendarWidget
QCalendarWidget类提供了以月为单位地日历部件.该部件允许用户以一种简单而直接的方式选择日期. #!/usr/bin/python # -*- coding: utf-8 -*- impo ...