Python多进程应用
在我之前的一篇博文中详细介绍了Python多线程的应用:
进程,线程,GIL,Python多线程,生产者消费者模型都是什么鬼
但是由于GIL的存在,使得python多线程没有充分利用CPU的多核,为了利用多核,我可以采用多进程;
1. 父进程与子进程
wiki上对于父进程与子进程的定义:
a)Parent process
In Unix-like operating systems, every process except process 0 (the swapper) is created when another process executes the fork() system call. The process that invoked fork is the parent process and the newly created process is the child process. Every process (except process 0) has one parent process, but can have many child processes.
In the Linux kernel, in which there is a very slim difference between processes and POSIX threads, there are two kinds of parent processes, namely real parent and parent. Parent is the process that receives the SIGCHLD signal on child's termination, whereas real parent is the thread that actually created this child process in a multithreaded environment. For a normal process, both these two values are same, but for a POSIX thread which acts as a process, these two values may be different.[1]
b)Child process
A child process in computing is a process created by another process (the parent process). This technique pertains to multitasking operating systems, and is sometimes called a subprocess or traditionally a subtask.
There are two major procedures for creating a child process: the fork system call (preferred in Unix-like systems and the POSIX standard) and the spawn (preferred in the modern (NT) kernel of Microsoft Windows, as well as in some historical operating systems).
即,Unix/Linux操作系统提供了一个fork()
系统调用,用于创建子进程;fork()非常特殊。普通的函数调用,调用一次,返回一次,但是fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。对于返回值,子进程永远返回0
,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID;
python的os模块,就含有fork函数:
#!/bin/env python
#coding:utf-8 import os
import time print('Process %s start...' % os.getpid())
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 3522 start...
i 3522 just created a child process 3523
i am child process 3523 and my parent is 3522
因为fork()调用一次,返回两次,所以得到上面的结果;这里注意:由于Windows没有fork
调用,上面的代码在Windows上无法运行;有了fork
调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。
2. multiprocessing
上面说到windows没有fork调用,那么如何在windows上实现多进程呢?
通过multiprocess模块,由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing
模块就是跨平台版本的多进程模块。
python中两个常用来处理进程的模块分别是subprocess和multiprocessing,其中subprocess通常用于执行外部程序,比如一些第三方应用程序,而不是Python程序。如果需要实现调用外部程序的功能,python的psutil模块是更好的选择,它不仅支持subprocess提供的功能,而且还能对当前主机或者启动的外部程序进行监控,比如获取网络、cpu、内存等信息使用情况,在做一些自动化运维工作时支持的更加全面。multiprocessing是python的多进程模块,主要通过启动python进程,调用target回调函数来处理任务。
注意:multiprocessing的方法与threading的方法类似,所以我们这里只给出示例代码,而不做详细介绍;
1)multiprocessing基本使用
与threading类似,也是有两种方式
a)直接调用
from multiprocessing import Process, freeze_support
import os processes = [] def run(item):
print('-'*50)
print('child process %s id: %s'%(item, os.getpid()))
print('child process %s parent id: %s' % (item, os.getppid())) def main():
#打印主进程进程号
print('main process id: ', os.getpid())
#创建多个子进程
for item in range(2):
p = Process(target=run, args=(item, ))
processes.append(p)
print('child process %s name: %s' % (item, p.name))
print('child process %s id: %s' % (item, p.pid)) for item in processes:
item.start() for item in processes:
item.join() if __name__ == '__main__':
main()
freeze_support()
b)面向对象方式调用
from multiprocessing import Process, freeze_support
import os processes = [] class MyProcess(Process):
def __init__(self, func, item):
super(MyProcess, self).__init__()
self.__func = func
self.__item = item def run(self):
self.__func(self.__item) def proc(item):
print('-'*50)
print('child process %s id: %s'%(item, os.getpid()))
print('child process %s parent id: %s' % (item, os.getppid())) def main():
#打印主进程进程号
print('main process id: ', os.getpid())
#创建多个子进程
for item in range(2):
p = MyProcess(proc, item)
processes.append(p)
print('child process %s name: %s' % (item, p.name))
print('child process %s id: %s' % (item, p.pid)) for item in processes:
item.start() for item in processes:
item.join() if __name__ == '__main__':
main()
freeze_support()
注:2.7中,if __name__ == '__main__'的代码块中必须加上freeze_support(),python3好像不需要了
结果:
main process id: 10972
child process 0 name: MyProcess-1
child process 0 id: None
child process 1 name: MyProcess-2
child process 1 id: None
--------------------------------------------------
child process 0 id: 10636
child process 0 parent id: 10972
--------------------------------------------------
child process 1 id: 8076
child process 1 parent id: 10972
2)daemon属性设置
from multiprocessing import Process
import time processes = [] def run(item):
time.sleep(1)
print('item: ', item) def main():
#创建多个子进程
for item in range(2):
p = Process(target=run, args=(item, ))
processes.append(p)
p.daemon = True for item in processes:
item.start() print('all done') if __name__ == '__main__':
main()
结果:
all done
注意daemon和threading的方式不同,这里是直接设置属性,而不是调用方法;另外要在start前设置daemon;
3)进程同步
既然进程之间不共享数据,为什么还有进程同步问题呢?如果多个进程打开同一个文件,在同一个屏幕输出呢?这些还是需要进程同步的,通过Lock
4)Semaphore
同threading.Semaphore()用法相同,只是创建的Semaphore需要作为参数传入子进程,因为进程间不共享资源
5)Event
同threading.Event()用法相同,只是创建的Event需要作为参数传入子进程
6)进程间通讯
因为进程之间不共享资源,我们先看一个例子证明一下:
from multiprocessing import Process processes = []
data_list = [] def run(lst, item):
lst.append(item)
print('%s : %s' % (item, lst)) def main():
for item in range(4):
p = Process(target=run, args=(data_list, item))
processes.append(p) for item in processes:
item.start() for item in processes:
item.join() print('final lst: ', data_list) if __name__ == '__main__':
main()
结果:
1 : [1]
2 : [2]
0 : [0]
3 : [3]
final lst: []
所以必须通过第三方实现进程间通讯,下面介绍3种方法
a)Queue
用法与queue.Queue在多线程中的应用相同,只是创建的queue要作为参数传入子进程
from multiprocessing import Process, Queue
import time q = Queue(10) def put(q):
for i in range(3):
q.put(i)
print('queue size after put: %s' % q.qsize()) def get(q):
print('queue size before get: %s' % q.qsize())
while not q.empty():
print('queue get: ', q.get()) def main():
p_put = Process(target=put, args=(q,))
p_get = Process(target=get, args=(q,))
p_put.start()
time.sleep(1)
p_get.start()
p_get.join()
print('all done') if __name__ == '__main__':
main()
结果:
queue size after put: 3
queue size before get: 3
queue get: 0
queue get: 1
queue get: 2
all done
b)Pipe
import multiprocessing
import time pipe = multiprocessing.Pipe() def send(pipe):
for i in range(5):
print("send: %s" % (i,))
pipe.send(i)
time.sleep(0.2) def recv_1(pipe):
while True:
print("rev_1:", pipe.recv())
time.sleep(1) def recv_2(pipe):
while True:
print("rev_2:", pipe.recv())
time.sleep(1) def main():
p_send = multiprocessing.Process(target=send, args=(pipe[0],))
p_recv_1 = multiprocessing.Process(target=recv_1, args=(pipe[1],))
p_recv_2 = multiprocessing.Process(target=recv_2, args=(pipe[1],)) p_send.start()
p_recv_1.start()
p_recv_2.start() p_send.join()
p_recv_1.join()
p_recv_2.join() if __name__ == "__main__":
main()
结果:
send: 0
rev_1: 0
send: 1
rev_2: 1
send: 2
send: 3
send: 4
rev_1: 2
rev_2: 3
rev_1: 4
c)Manager
相当相当给力,上面的Queue,Pipe仅仅可以传递数据,而不能做到数据共享(不同进程修改同一份数据),但是Manger可以做到数据共享
看一下官方文档:
A manager object returned by Manager()
controls a server process which holds Python objects and allows other processes to manipulate them using proxies.
A manager returned by Manager()
will support types list
, dict
, Namespace
, Lock
, RLock
, Semaphore
, BoundedSemaphore
, Condition
, Event
, Queue
, Value
and Array
.
from multiprocessing import Process, Manager def run(d, l):
d['name'] = 'winter'
l.reverse() def main():
p = Process(target=run, args=(d, l, ))
p.start()
p.join() print('final dict: ', d)
print('final list: ', l) if __name__ == "__main__":
mgmt = Manager()
d = mgmt.dict()
l = mgmt.list(range(10))
main()
注意:mgmt = Manger()必须放在if __name__ == "__main__"的代码块中,不然报freeze_support()的错误
而且,注意这里:
Server process managers are more flexible than using shared memory objects because they can be made to support arbitrary object types. Also, a single manager can be shared by processes on different computers over a network. They are, however, slower than using shared memory.
还可以在不同主机之间共享数据;
7)进程池Pool
如果要启动大量的子进程,可以用进程池pool批量创建子进程:Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行。
有两种方法:阻塞方法Pool.apply()和非阻塞方法Pool.apply_async()
a)阻塞方法Pool.apply()
import multiprocessing
import time def func(name):
print("start: %s" % name)
time.sleep(2)
return 'end: %s' % name if __name__ == "__main__":
name_list = ['winter', 'elly', 'james', 'yule']
res_list = []
# 创建一个进程总数为3的进程池
pool = multiprocessing.Pool(3)
for member in name_list:
# 创建子进程,并执行,不需要start
res = pool.apply(func, (member,))
print(res)
pool.close()
# 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool
pool.join()
print("all done...")
结果:
start: winter
end: winter
start: elly
end: elly
start: james
end: james
start: yule
end: yule
all done...
发现,阻塞方式下,进程是一个一个执行的,还是串行,所以apply用的少;
注意两点:
1. 进程池执行子进程不需要start;
2. 调用join()
之前必须先调用close()
,调用close()
之后就不能继续添加新的Process
了;
b)非阻塞方法Pool.apply_async()
import multiprocessing
import time def func(name):
print("start: %s" % name)
time.sleep(2)
return 'end: %s' % name def func_exp(msg):
print('callback: %s' % msg) if __name__ == "__main__":
name_list = ['winter', 'elly', 'james', 'yule']
res_list = []
# 创建一个进程总数为3的进程池
pool = multiprocessing.Pool()
for member in name_list:
# 创建子进程,并执行,不需要start
res = pool.apply_async(func, (member,), callback=func_exp)
#注意这里是append了res,不是res.get(),不然又要阻塞了
res_list.append(res)
for res_mem in res_list:
print(res_mem.get())
pool.close()
# 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool
pool.join()
print("all done...")
结果:
start: winter
start: elly
start: james
start: yule
callback: end: winter
end: winter
callback: end: elly
end: elly
callback: end: james
end: james
callback: end: yule
end: yule
all done...
结果分析:
1. 可以看到非阻塞情况下,充分利用了多核,实现了并行;
2. apply_async方法含有callback参数,可以用于回调
3.为什么apply方法是阻塞的呢?到底阻塞在了哪里呢?同时apply_async方法做了什么改进呢?
查看apply方法源码:
def apply(self, func, args=(), kwds={}):
'''
Equivalent of `func(*args, **kwds)`.
'''
assert self._state == RUN
return self.apply_async(func, args, kwds).get()
apply方法最终执行了self.apply_async(func, args, kwds).get(),同样调用了apply_async()方法,只是对结果执行了get()方法;阻塞就是阻塞在了这里;
那我修改一下apply_async()的代码是不是可以让apply_async()可以变成阻塞的呢?试一下
import multiprocessing
import time def func(name):
print("start: %s" % name)
time.sleep(2)
return 'end: %s' % name def func_exp(msg):
print('callback: %s' % msg) if __name__ == "__main__":
name_list = ['winter', 'elly', 'james', 'yule']
# 创建一个进程总数为3的进程池
pool = multiprocessing.Pool()
for member in name_list:
# 创建子进程,并执行,不需要start
res = pool.apply_async(func, (member,), callback=func_exp)
print(res.get())
pool.close()
# 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool
pool.join()
print("all done...")
注意红色部分是我修改的编码,结果果然变成了阻塞状态:
start: winter
callback: end: winter
end: winter
start: elly
callback: end: elly
end: elly
start: james
callback: end: james
end: james
start: yule
callback: end: yule
end: yule
all done...
c)进程池该设置多少个进程数?
既然多进程可以利用多核,那么是不是创建越多的进程越好呢?不是的,因为进程的切换成本高,所以数量太多的进程来回切换反而会降低效率!
进程数是一个经验值,和系统的硬件资源有很大关系;最优的进程数需要通过不断调整得出;
Pool创建时,进程池的进程数默认大小为CPU的逻辑CPU数目(内核线程数);
经验上来说:
Python多进程应用的更多相关文章
- Python多进程编程
转自:Python多进程编程 阅读目录 1. Process 2. Lock 3. Semaphore 4. Event 5. Queue 6. Pipe 7. Pool 序. multiproces ...
- Python多进程(1)——subprocess与Popen()
Python多进程方面涉及的模块主要包括: subprocess:可以在当前程序中执行其他程序或命令: mmap:提供一种基于内存的进程间通信机制: multiprocessing:提供支持多处理器技 ...
- Python多进程使用
[Python之旅]第六篇(六):Python多进程使用 香飘叶子 2016-05-10 10:57:50 浏览190 评论0 python 多进程 多进程通信 摘要: 关于进程与线程的对比, ...
- python多进程断点续传分片下载器
python多进程断点续传分片下载器 标签:python 下载器 多进程 因为爬虫要用到下载器,但是直接用urllib下载很慢,所以找了很久终于找到一个让我欣喜的下载器.他能够断点续传分片下载,极大提 ...
- Python多进程multiprocessing使用示例
mutilprocess简介 像线程一样管理进程,这个是mutilprocess的核心,他与threading很是相像,对多核CPU的利用率会比threading好的多. import multipr ...
- Python多进程并发(multiprocessing)用法实例详解
http://www.jb51.net/article/67116.htm 本文实例讲述了Python多进程并发(multiprocessing)用法.分享给大家供大家参考.具体分析如下: 由于Pyt ...
- python 多进程开发与多线程开发
转自: http://tchuairen.blog.51cto.com/3848118/1720965 博文作者参考的博文: 博文1 博文2 我们先来了解什么是进程? 程序并不能单独运行,只有将程 ...
- Python多进程----从入门到放弃
Python多进程 (所有只写如何起多进程跑数据,多进程数据汇总处理不提的都是耍流氓,恩,就这么任性) (1)进程间数据问题,因为多进程是完全copy出的子进程,具有独立的单元,数据存储就是问题了 ( ...
- day-4 python多进程编程知识点汇总
1. python多进程简介 由于Python设计的限制(我说的是咱们常用的CPython).最多只能用满1个CPU核心.Python提供了非常好用的多进程包multiprocessing,他提供了一 ...
- python 多进程 logging:ConcurrentLogHandler
python 多进程 logging:ConcurrentLogHandler python的logging模块RotatingFileHandler仅仅是线程安全的,如果多进程多线程使用,推荐 Co ...
随机推荐
- JS中如何巧妙的用事件委托
常见场景:页面有多个相同的按钮需要绑定同样的事件逻辑. 如下HTML,实现:点击每个按钮,当它的 data-id不为null的时候输出它的data-id(实际业务中会有更复杂的逻辑) <ul i ...
- JVM菜鸟进阶高手之路三
转载请注明原创出处,谢谢! 笨神大大分享: 小程序里面搜索:JVMPocket,这个小程序是笨神大大提供的,里面可以搜索相关JVM参数,用法. -XX:MaxTenuringThreshold,这个参 ...
- Android 8.0 功能和 API
Android 8.0 为用户和开发者引入多种新功能.本文重点介绍面向开发者的新功能. 用户体验 通知 在 Android 8.0 中,我们已重新设计通知,以便为管理通知行为和设置提供更轻松和更统一的 ...
- ajax 发送json 后台接收 遍历保存进数据库
前台怎么拿参数的我就不管了我也不会 反正用这个ajax没错 ajax 代码 一定要写明http请求类型 { contentType:"application/x-www-form-ur ...
- Finally-操作返回值
Finally中操作返回会出现一个问题?直接看代码 static int M1() { ; try { result = result + ; //======引发异常的代码========== , ...
- VS2015 + EF6连接MYSQL
ADO.NET Entity Framework 是微软以 ADO.NET 为基础所发展出来的对象关系对应 (O/R Mapping) 解决方案,不仅支持SQL Server,还支持MySQL.Ora ...
- 复选框demo
本篇文章是关于复选框的,有2种形式:1.全选.反选由2个按钮实现:2.全选.反选由一个按钮实现. <!DOCTYPE html> <html> <head> < ...
- socket端口外网无法连接解决方法
用socket做了个程序,本地测试没有问题,发布到服务器上时连接不上,用telnet测试连接失败 服务器上netstat -a 查看端口情况,127.0.0.1绑定端口9300处于监听状态,如下图: ...
- 初学node.js有感二
node.js进阶 一.回顾与继续 对于一种语言的认识都是经历这样的一个过程的,首先从原生的环境(CMD)中开始学习,找到一门语言之间各种引用的本质和相互之间的调用方式,明澈各种依赖关系,在这个基 ...
- hdu 2609 How many 最小表示法
How many Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Su ...