Created on 2020-01-04
@author 假如我年华正好
参考资料:
multiprocessing
模块源码:https://github.com/python/cpython/tree/3.6/Lib/multiprocessingmultiprocessing
模块官方文档:https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing
并发 VS 并行:
多线程模块:threading
;多进程模块:multiprocessing
每一个应用程序都有一个自己的进程。操作系统会为这些进程分配一些执行资源,例如内存空间等。
每一个进程至少有一个线程,多个线程共享这些内存空间,并由操作系统调用,以便并行计算。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响;
而线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。
多线程可以直接共享全局变量,但是要注意资源竞争的问题;
⭐而进程没有任何共享状态,进程修改的数据,改动仅限于该进程内 ,,多进程间的通信比较麻烦。
python 的多进程在 windows 下存在一些奇怪的坑,还是 Linux 大法好。
子进程在 Spyder、jupyter 等IDE下运行时是没有输出的,就算报错了也不知道,,
在 pycharm 和 cmd 中可以看到输出结果,,
在 cmd (shell)中执行 python 脚本的命令为:python xxx.py
(需要先切换至.py所在目录)。
执行子进程的代码必须要放在if __name__ == "__main__":
语句后面,否则子进程不执行!
但是创建子进程的语句可以放在前面。
多线程之间共享全局变量,但是多进程之间是不共享的,,多进程之间的通信比较麻烦,,
尤其是想共享类的实例对象的时候,需要自定义 Manager()
对象,,
Manager()
创建的代理对象可以直接调用类的方法,却不可以直接读取类的属性!!
如果要获取类的属性,需要通过在类中定义相应的方法!!
可以在子进程调用的函数里打印子进程的id:print("in process pid=%d" % (os.getpid(), ))
windows 在 shell 中查看当前python进程:wmic process where name="python.exe"
本文的代码列表:
# 3.1 版本一:测试子进程输出的错误示范❌
# 3.2 版本二:测试子进程输出的正确示范✔
# 4.2-(1)测试1:一个子进程
# 4.2-(1) 测试2:两个子进程
# 4.2-(1)测试3:很多子进程
# 4.2-(1) 测试4:不创建代理对象,直接定义全局变量,是不共享的
# 4.2-(2) 测试1:用代理对象可直接调用类方法
# 4.2-(2) 测试2:用代理对象无法直接读取类属性
# 4.2-(2)测试3:多进程共享类的实例对象
没有输出可能情况:
运行了,但是没有打印结果
压根没运行
下面用两个版本的py脚本进行测试:
运行结果
结果分析:
主进程很快结束,可见子进程没有执行。
shell 和 pycharm 会打印子进程的报错信息,,
而 spyder 和 jupyter notebook 不会打印子进程的情况,报错了也不打印,所以我们无法知道子进程到底有没有在执行。
以下是代码和结果:
# 3.1 版本一:测试子进程输出的错误示范❌
import multiprocessing
import time
#创建一个简单进程每隔x秒打印时间一次
def clock(wait_time):
i = 0
while i<10:
print("now is %s" %time.ctime())
time.sleep(wait_time)
i += 1
p = multiprocessing.Process(target=clock, args=(5,))
print("开始运行子进程...")
p.start()
p.join()
print("子进程运行结束!")
开始运行子进程...
子进程运行结束!
以下是在 shell 和 pycharm 中的输出:
(base) D:\github\CategoricalEncoder\scr>python test3.py
开始运行子进程...
开始运行子进程...
Traceback (most recent call last):
File "" , line 1, in <module>
File "C:\Users\admin\Anaconda3\lib\multiprocessing\spawn.py", line 105, in spawn_main
exitcode = _main(fd)
File "C:\Users\admin\Anaconda3\lib\multiprocessing\spawn.py", line 114, in _main
prepare(preparation_data)
File "C:\Users\admin\Anaconda3\lib\multiprocessing\spawn.py", line 225, in prepare
_fixup_main_from_path(data['init_main_from_path'])
File "C:\Users\admin\Anaconda3\lib\multiprocessing\spawn.py", line 277, in _fixup_main_from_path
run_name="__mp_main__")
File "C:\Users\admin\Anaconda3\lib\runpy.py", line 263, in run_path
pkg_name=pkg_name, script_name=fname)
File "C:\Users\admin\Anaconda3\lib\runpy.py", line 96, in _run_module_code
mod_name, mod_spec, pkg_name, script_name)
File "C:\Users\admin\Anaconda3\lib\runpy.py", line 85, in _run_code
exec(code, run_globals)
File "D:\github\CategoricalEncoder\scr\test3.py", line 20, in <module>
p.start()
File "C:\Users\admin\Anaconda3\lib\multiprocessing\process.py", line 112, in start
self._popen = self._Popen(self)
File "C:\Users\admin\Anaconda3\lib\multiprocessing\context.py", line 223, in _Popen
return _default_context.get_context().Process._Popen(process_obj)
File "C:\Users\admin\Anaconda3\lib\multiprocessing\context.py", line 322, in _Popen
return Popen(process_obj)
File "C:\Users\admin\Anaconda3\lib\multiprocessing\popen_spawn_win32.py", line 33, in __init__
prep_data = spawn.get_preparation_data(process_obj._name)
File "C:\Users\admin\Anaconda3\lib\multiprocessing\spawn.py", line 143, in get_preparation_data
_check_not_importing_main()
File "C:\Users\admin\Anaconda3\lib\multiprocessing\spawn.py", line 136, in _check_not_importing_main
is not going to be frozen to produce an executable.''')
RuntimeError:
An attempt has been made to start a new process before the
current process has finished its bootstrapping phase.
This probably means that you are not using fork to start your
child processes and you have forgotten to use the proper idiom
in the main module:
if __name__ == '__main__':
freeze_support()
...
The "freeze_support()" line can be omitted if the program
is not going to be frozen to produce an executable.
子进程运行结束!
增加 if __name__ == "__main__":
语句
运行结果:
结果分析:
jupyter notebook 中主进程很快结束,说明子进程依然没有执行——因为jupyter notebook 中if __name__ == "__main__":
语句并不生效。
spyder 中可以正常执行子进程了,但是无法打印子进程的输出。——所以此时虽然我们知道子进程在执行了,但还是看不到具体的执行情况。
shell 和 pycharm 中主进程、子进程正常执行和打印。
以下是代码和结果:
# 3.2 版本二:测试子进程输出的正确示范✔
import multiprocessing
import time
#创建一个简单进程每隔x秒打印时间一次
def clock(wait_time):
i = 0
while i<10:
print("now is %s" %time.ctime())
time.sleep(wait_time)
i += 1
if __name__ == "__main__":
p = multiprocessing.Process(target=clock, args=(5,))
print("开始运行子进程...")
p.start()
p.join()
print("子进程运行结束!")
开始运行子进程...
子进程运行结束!
以下是在 shell 和 pycharm 中的输出:
(base) D:\github\CategoricalEncoder\scr>python test3.py
开始运行子进程...
now is Fri Jan 3 23:16:06 2020
now is Fri Jan 3 23:16:11 2020
now is Fri Jan 3 23:16:16 2020
now is Fri Jan 3 23:16:21 2020
now is Fri Jan 3 23:16:26 2020
now is Fri Jan 3 23:16:31 2020
now is Fri Jan 3 23:16:36 2020
now is Fri Jan 3 23:16:41 2020
now is Fri Jan 3 23:16:46 2020
now is Fri Jan 3 23:16:51 2020
子进程运行结束!
python进程在Win下运行有诸多限制,
第一,子进程在Spyder、jupyter等IDE下运行时是没有输出的,就算报错了也不知道,
—— 因为IDE输出窗口输出的是主进程的内容,我们创建的子进程是无法输出的 (来自yungeisme)
—— 因为 multiprocessing 模块在交互模式是不支持的 (来自 chenpe32cp)
要看输出结果,要在cmd下运行python脚本,命令为:python xxx.py
第二,执行子进程的代码必须要放在if __name__ == "__main__":
语句后面,否则子进程不执行! (下面是本人亲测得出的结论:)
p = multiprocessing.Process(target=clock, args=(5,))
放到前面,子进程还是可以正常执行;(所以来自yungeisme的限制二的表述应该是有误的)p.start()
也放到前面,子进程就会报错了!多线程之间共享全局变量,但是多进程之间是不共享的,,
多进程之间的通信比较麻烦,,
尤其是共享类实例的时候!!搜索相关的内容好少,,这里记录一下摸索了两天的一些收获,
首先感谢参考:https://blog.csdn.net/jacke121/article/details/82630693
参考文档
进程间可以交换对象,即一个进程可以发送东西出去,另一个进程可以接收东西,,
multiprocessing
模块提供了支持进程间进行通信的方法:
参考文档
保持进程间的同步:
threading
模块一样,可以用锁:Lock不用锁的话,多个进程的结果和输出可能会混淆在一起。
参考文档
进程间共享状态
(其实,在并行运算时,通常最好尽可能避免共享状态。多进程时尤其如此。实在需要共享的话,multiprocessing
也提供了一些方法)
Shared memory:共享内存,使用 Value
或 Array
。
Server process:
Manager
:服务器进程,代理对象(Proxy Objects)list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value
and Array
1. Shared memory VS 2. Server process
用Manager()
创建代理对象,代理对象可以被多个进程共享。例如:
Manager().list()
——列表Manager().dict()
——字典Manager().Value()
——值# 4.2-(1)测试1:一个子进程
from multiprocessing import Process, Manager
def f(d, l, v):
d[1] = 'a'
l.reverse()
v.value += 1
if __name__ == '__main__':
with Manager() as manager:
d = manager.dict()
l = manager.list(range(5))
v = manager.Value('v', 0)
p = Process(target=f, args=(d, l, v))
p.start()
p.join()
print(d)
print(l)
print(v.value)
输出结果为:
{1: 'a'}
[4, 3, 2, 1, 0]
1
# 4.2-(1) 测试2:两个子进程
from multiprocessing import Process, Manager
def f1(d, l, v):
d[1] = 'a'
l.reverse()
v.value += 1
def f2(d, l, v):
d[2] = 'b'
l.append(1000)
v.value += 1
if __name__ == '__main__':
with Manager() as manager:
d = manager.dict()
l = manager.list(range(5))
v = manager.Value('v', 0)
p1 = Process(target=f1, args=(d, l, v))
p1.start()
p2 = Process(target=f2, args=(d, l, v))
p2.start()
p1.join()
p2.join()
print(d,l,v.value)
输出结果为:
{1: 'a', 2: 'b'} [4, 3, 2, 1, 0, 1000] 2
# 4.2-(1)测试3:很多子进程
import os
from multiprocessing import Process, Manager
def f(v):
v.value += 1
print("in process pid=%d ,v=%s" % (os.getpid(), v.value)) #打印子进程信息
if __name__ == '__main__':
with Manager() as manager:
v = manager.Value('v', 0)
ps = [Process(target=f, args=(v,)) for i in range(10)]
for p in ps:
p.start()
for p in ps:
p.join()
print(v.value)
输出结果为:
in process pid=348 ,v=1
in process pid=11124 ,v=2
in process pid=20084 ,v=3
in process pid=13924 ,v=4
in process pid=984 ,v=5
in process pid=2860 ,v=6
in process pid=7776 ,v=7
in process pid=2276 ,v=8
in process pid=11380 ,v=9
in process pid=6696 ,v=10
10
如果不用Manager()
创建代理对象,直接定义全局变量,子进程之间是不共享的:
# 4.2-(1) 测试4:不创建代理对象,直接定义全局变量,是不共享的
import os
from multiprocessing import Process
def f(v):
v += 1
print("in process pid=%d ,v=%s" % (os.getpid(), v))
if __name__ == '__main__':
v = 0
ps = [Process(target=f, args=(v,)) for i in range(10)]
for p in ps:
p.start()
for p in ps:
p.join()
print(v)
输出结果为:
in process pid=15476 ,v=1
in process pid=5848 ,v=1
in process pid=16452 ,v=1
in process pid=12944 ,v=1
in process pid=15276 ,v=1
in process pid=18448 ,v=1
in process pid=5272 ,v=1
in process pid=16496 ,v=1
in process pid=7212 ,v=1
in process pid=8652 ,v=1
0
如果要共享类的实例对象,需要 Customized managers:
——creates a subclass of BaseManager
and uses the register()
classmethod to register new types or callables with the manager class.
具体流程如下:
# 导入模块
from multiprocessing.managers import BaseManager
# 定义需要共享的类
class myClass:
def __init__(self):
# ...
pass
def foo(self):
#...
pass
# 新建 BaseManager 子类,空的就行
class MyManager(BaseManager):
pass
# 把需要共享的类注册到 MyManager 里
# 第二个参数是个可调用对象(例如类),第一个参数则是之后用来引用该对象的名字
MyManager.register('myClassName', myClass)
# 使用
if __name__ == "__main__":
with MyManager() as manager:
obj = manager.myClassName() # 注意这里要用myClassName,而不是myClass
# 代理对象调用类方法
obj.foo()
注意:
代理对象可以直接调用类方法,,
但是不能直接读取类属性!!
AttributeError: 'AutoProxy[xxx]' object has no attribute 'xxx'
以下是测试例子:
# 4.2-(2) 测试1:用代理对象可直接调用类方法
from multiprocessing.managers import BaseManager
class MathsClass:
def add(self, x, y):
return x + y
def mul(self, x, y):
return x * y
class MyManager(BaseManager):
pass
MyManager.register('Maths', MathsClass)
if __name__ == '__main__':
with MyManager() as manager:
maths = manager.Maths()
print(maths.add(4, 3)) # prints 7
print(maths.mul(7, 8)) # prints 56
# 4.2-(2) 测试2:用代理对象无法直接读取类属性
from multiprocessing.managers import BaseManager
class MathsClass:
def __init__(self):
self.value = 0
#==============================================
# 在类内写一个读取类属性的方法
def get_value(self):
return self.value
#==============================================
def add(self, x, y):
self.value += 1
return x + y
def mul(self, x, y):
self.value += 1
return x * y
class MyManager(BaseManager):
pass
MyManager.register('Maths', MathsClass)
if __name__ == '__main__':
with MyManager() as manager:
maths = manager.Maths()
# 直接读取会报错:AttributeError: 'AutoProxy[Maths]' object has no attribute 'value'
#print(maths.value)
# 要通过调用方法来读取
print(maths.get_value()) # prints 2
具体流程如下:
# 导入模块
from multiprocessing.managers import BaseManager
# 定义需要共享的类
class myClass:
def __init__(self):
# do something...
pass
def foo(self):
# do something...
pass
# 新建 BaseManager 子类,空的就行
class MyManager(BaseManager):
pass
# 把需要共享的类注册到 MyManager 里
# 第二个参数是个可调用对象(例如类),第一个参数则是之后用来引用该对象的名字
MyManager.register('myClassName', myClass)
# 定义 Manager2,用来启动 MyManager
def Manager2():
m = MyManager
m.start()
return m
# 定义子进程要调用的函数,在函数中放类方法
# 如果多个子进程存在资源竞争问题,则要加锁;确定不存在也可以不加
def func(obj, lock):
with lock:
obj.foo() # 调用 myClass类中的方法
# do something another...
# 使用
if __name__ == "__main__":
with MyManager() as manager:
obj = manager.myClassName() # 注意这里要用myClassName,而不是myClass
# 创建锁
lock = Lock()
# 创建子线程,执行函数 func
process = [Process(target=func, args=(obj, lock)) for i in range(3)]
for p in process:
p.start()
for p in process:
p.join()
# do something another...
注意:用with MyManager() as manager:
比直接用manager = Manager2()
更安全。
# 4.2-(2)测试3:多进程共享类的实例对象
# -*- coding: utf-8 -*-
from multiprocessing import Process, Value, Lock
from multiprocessing.managers import BaseManager
import time
import os
class Employee(object):
def __init__(self, name, salary):
self.name = name
#self.salary = Value('i', salary)
self.salary = salary
self.data=[]
def increase(self):
self.name = self.name + self.name[0]
#self.salary.value += 100
self.salary += 100
self.data.append(self.salary)
def getInfo(self):
#return self.name + ':' + str(self.salary.value) + str(self.data)
return self.name + ':' + str(self.salary) + str(self.data)
class MyManager(BaseManager):
pass
def Manager2():
m = MyManager()
m.start()
return m
MyManager.register('Employee', Employee)
def func(em, lock):
with lock:
time.sleep(1)
em.increase()
print("in process pid=%d:%s" % (os.getpid(), em.getInfo()))
if __name__ == '__main__':
with Manager2() as manager:
em = manager.Employee('小a', 0)
lock = Lock()
proces = [Process(target=func, args=(em, lock)) for i in range(3)]
for p in proces:
p.start()
for p in proces:
p.join()
print(em.getInfo())
输出结果为:
in process pid=3676:小a小:100[100]
in process pid=15568:小a小小:200[100, 200]
in process pid=11812:小a小小小:300[100, 200, 300]
小a小小小:300[100, 200, 300]
参考来源:Linux 和 Windows 查看当前运行的 python 进程及 GPU、CPU、磁盘利用率
Linux
在 shell 中执行如下指令:
ps -ef | grep python
或者
ps aux | grep python
Windows
在 shell 中执行如下指令:
wmic process where name="python.exe"
或者
wmic process where name="python.exe" list full
wmic process where name="python.exe" list brief
查看其它程序进程,只需要将 python 改个名即可。
--------完--------