在 IBM® developerWorks® 早期文章 中我演示了使用 Python 实现线程式编程种简单且有效模式但是这种思路方法个缺陷就是它并不总是能够提高应用速度全局解释器锁(Global Interpreter LockGIL)将线程有效地限制到个核中如果需要使用计算机中所有核那么通常都需通过 对 经常使用 fork 操作来实现从而提高速度处理进程组是件困难事情为了在进程的间进行通信需要对所有进行协调这通常会使事情变得更复杂
幸运是自 2.6 版本起Python 包括了个名为 “多进程(multiprocessing)” 模块来帮助处理进程该进程模块 API 和线程 API 工作方式有些相似点但是也存在些需要特别注意区别的处主要区别的就是进程拥有些微妙底层行为这是高级 API 永远无法完全抽象出来可以从多进程模块官方文档中了解有关这方面内容(参见 参考资料 小节)
fork 介绍
进程和线程在并发性工作原理方面存在些明显差异通过阅读我撰写有关线程 developerWorks 文章可以进步了解这些差异(参见 参考资料)在进程执行 fork 时操作系统将创建具有新进程 ID 新子进程复制父进程状态(内存、环境变量等)首先在我们实际使用进程模块的前先看下 Python 中个非常基本 fork 操作
fork.py
#!/usr/bin/env python
"""A basic fork in action"""
import os
def my_fork:
child_pid = os.fork
child_pid 0:
pr "Child Process: PID# %s" % os.getpid
:
pr "Parent Process: PID# %s" % os.getpid
__name__ "____":
my_fork
现在来看下以上代码输出:
mac% python fork.py
Parent Process: PID# 5285
Child Process: PID# 5286
在下个举例中增强 fork 代码并设置个环境变量该环境变量随后将被复制到子进程中下面给出了相应代码:
举例 1. Python 中 fork 操作
#!/usr/bin/env python
"""A fork that demonstrates a copied environment"""
import os
from os import environ
def my_fork:
environ['FOO']="baz"
pr "FOO environmental variable to: %s" % environ['FOO']
environ['FOO']="bar"
pr "FOO environmental variable changed to: %s" % environ['FOO']
child_pid = os.fork
child_pid 0:
pr "Child Process: PID# %s" % os.getpid
pr "Child FOO environmental variable %s" % environ['FOO']
:
pr "Parent Process: PID# %s" % os.getpid
pr "Parent FOO environmental variable %s" % environ['FOO']
__name__ "____":
my_fork
下面给出了 fork 输出:
mac% python env_fork.py
FOO environmental variable to: baz
FOO environmental variable changed to: bar
Parent Process: PID# 5333
Parent FOO environmental variable bar
Child Process: PID# 5334
Child FOO environmental variable bar
在输出中可以看到 “修改后” 环境变量 FOO 留在了子进程和父进程中您可以通过在父进程中再次修改环境变量来进步测试这个举例您将看到子进程现在是完全独立它有了自己生命注意子进程模块也可用于 fork 进程但是实现方式没有多进程模块那么复杂
多进程介绍
现在您已经了解 Python fork 操作基本知识让我们通过个简单例子了解它在更高级多进程库中使用在这个举例中仍然会出现 fork但是已经为我们处理了大部分标准工作
举例 2. 简单多进程
#!/usr/bin/env python
from multiprocessing import Process
import os
import time
def sleeper(name, seconds):
pr 'starting child process with id: ', os.getpid
pr 'parent process:', os.getppid
pr 'sleeping for %s ' % seconds
time.sleep(seconds)
pr "Done sleeping"
__name__ '____':
pr "in parent process (id %s)" % os.getpid
p = Process(target=sleeper, args=('bob', 5))
p.start
pr "in parent process after child process start"
pr "parent process about to join child process"
p.join
pr "in parent process after child process join"
pr "parent process exiting with id ", os.getpid
pr "The parent's parent process:", os.getppid
如果查看输出将会看到下面内容:
mac% python simple.py
in parent process (id 5245)
in parent process after child process start
parent process about to join child process
starting child process with id: 5246
parent process: 5245
sleeping for 5
Done sleeping
in parent process after child process join
parent process exiting with id 5245
The parent's parent process: 5231
可以看到从主进程分出了个子进程该子进程随后休眠了 5 秒种子进程分配是在 p.start 时发生在下节中您将看到这个基础将扩展为更大
构建异步 Net-SNMP 引擎
到目前为止您尚未构建任何特别有用内容下个举例将解决个实际问题为 Net-SNMP 异步生成 Python 绑定默认情况下Net-SNMP 将阻塞每个 Python 使用多进程库可以非常简单地将 Net-SNMP 库转换为完全异步操作
在开始的前需要检查是否安装了些必备内容以便使用 Python 2.6 多进程库和 Net-SNMP 绑定:
下载 Python 2.6 并针对所使用操作系统进行编译:Python 2.6 下载
调整 shell 路径这样在输入 python 时就会启动 Python 2.6例如如果将 Python 编译到 /usr/local/bin/您就需要预先处理 $PATH 变量从而确保它位于个较旧 Python 版本的前
下载并安装设置工具:设置工具
下载 Net-SNMP除了使用其他操作系统所需标记(参见相应 README 文件)外另外使用个 “--with-python-modules” 标记进行配置 ./configure --with-python-modules
按如下所示编译 Net-SNMP:
---------------------------------------------------------
Net-SNMP configuration summary:
---------------------------------------------------------
SNMP Versions Supported: 1 2c 3
Net-SNMP Version: 5.4.2.1
Building for: darwin9
Network transport support: Callback Unix TCP UDP
SNMPv3 Security Modules: usm
Agent MIB code: default_modules => snmpv3mibs mibII ucd_snmp notication
notication-log-mib target agent_mibs agentx disman/event disman/schedule utilities
Embedded Perl support: enabled
SNMP Perl modules: building -- embeddable
SNMP Python modules: building for /usr/local/bin//python
Authentication support: MD5 SHA1
Encryption support: DES AES
]]
查看以下模块代码您将随后运行它
举例 3. Net-SNMP 多进程包装器
#!/usr/bin/env python2.6
"""
This is a multiprocessing wrapper for Net-SNMP.
This makes a synchronous API asynchronous by combining
it with Python2.6
"""
import netsnmp
from multiprocessing import Process, Queue, current_process
HostRecord:
"""This creates a host record"""
def __init__(self,
hostname = None,
query = None):
self.hostname = hostname
self.query = query
SnmpSession:
"""A SNMP Session"""
def __init__(self,
oid = "sysDescr",
Version = 2,
DestHost = "localhost",
Community = "public",
Verbose = True,
):
self.oid = oid
self.Version = Version
self.DestHost = DestHost
self.Community = Community
self.Verbose = Verbose
self.var = netsnmp.Varbind(oid, 0)
self.hostrec = HostRecord
self.hostrec.hostname = self.DestHost
def query(self):
"""Creates SNMP query
Fills out a Host Object and s result
"""
try:
result = netsnmp.snmpget(self.var,
Version = self.Version,
DestHost = self.DestHost,
Community = self.Community)
self.hostrec.query = result
except Exception, err:
self.Verbose:
pr err
self.hostrec.query = None
finally:
self.hostrec
def make_query(host):
"""This does the actual snmp query
This is a bit fancy as it accepts both instances
of SnmpSession and host/ip addresses. This
allows a user to customize mass queries with
subs of dferent hostnames and community s
"""
isinstance(host,SnmpSession):
host.query
:
s = SnmpSession(DestHost=host)
s.query
# Function run by worker processes
def worker(input, output):
for func in iter(input.get, 'STOP'):
result = make_query(func)
output.put(result)
def :
"""Runs everything"""
#clients
hosts = ["localhost", "localhost"]
NUMBER_OF_PROCESSES = len(hosts)
# Create queues
task_queue = Queue
done_queue = Queue
#submit tasks
for host in hosts:
task_queue.put(host)
#Start worker processes
for i in range(NUMBER_OF_PROCESSES):
Process(target=worker, args=(task_queue, done_queue)).start
# Get and pr results
pr 'Unordered results:'
for i in range(len(hosts)):
pr '\t', done_queue.get.query
# Tell child processes to stop
for i in range(NUMBER_OF_PROCESSES):
task_queue.put('STOP')
pr "Stopping Process #%s" % i
__name__ "____":
这里有两个类个 HostRecord 类和个 SnmpSession 类SnmpSession 类包含个使用 Net-SNMP SNMP 库实际执行查询思路方法由于般都会进行阻塞因此需要导入多进程库并使用 Process 运行它此外传入个 task_queue 和个 done_queue这样可以同步并保护进出进程池数据如果对线程比较熟悉话将会注意到这种方式非常类似于线程 API 使用思路方法
需要特别关注下主中 #clients 部分主机列表注意可以对 50 或 100 台或更多主机运行异步 SNMP 查询具体取决于当前使用硬件NUMBER_OF_PROCESSES 变量设置非常简单只是被设置为主机列表中主机数最终最后两个部分在处理过程中从队列获取结果然后将个 “STOP” 消息放到队列中表示可以终止进程
如果在对 Net-SNMP 进行监听 OS X 机器上运行代码那么将会得到如下所示非阻塞输出:
mac% time python multisnmp.py
Unordered results:
('Darwin mac.local 9.6.0 Darwin Kernel Version 9.6.0: Mon Nov 24 17:37:00 PST 2008;
root:xnu-1228.9.59~1/RELEASE_I386 i386',)
('Darwin mac.local 9.6.0 Darwin Kernel Version 9.6.0: Mon Nov 24 17:37:00 PST 2008;
root:xnu-1228.9.59~1/RELEASE_I386 i386',)
Stopping Process #0
Stopping Process #1
python multisnmp.py 0.18s user 0.08s system 107% cpu 0.236 total
配置 OS X SNMPD
如果希望配置 OS X SNMP Daemon 以针对本文进行测试那么需要执行下面操作首先在 shell 中使用 3个命令重写配置文件:
$ sudo cp /etc/snmp/snmpd.conf /etc/snmp/snmpd.conf.bak.testing
$ sudo echo "rocommunity public" > /etc/snmp/snmpd.conf
$ sudo snmpd
这将有效地备份您配置生成个新配置然后重新启动 snmpd步骤和许多 UNIX 平台类似但步骤 3 是除外该步骤需要重新启动 snmpd或发送个 HUP如果希望 OS X 在启动后永久运行 snmpd那么可以按如下所示编辑这个 plist 文件:
//Library/LaunchDaemons/org.net-snmp.snmpd.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD
PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
plist version="1.0">
<dict>
<key>Disabled</key>
<false/>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<>org.net-snmp.snmpd</>
<key>OnDemand</key>
<false/>
<key>Program</key>
<>/usr/sbin/snmpd</>
<key>ProgramArguments</key>
<.gif' />>
<>snmpd</>
<>-f</>
</.gif' />>
<key>RunAtLoad</key>
<true/>
<key>ServiceIPC</key>
<false/>
</dict>
</plist>
如果希望对多台机器进行测试那么使用下面内容替换主机行就可以轻松执行修改:
hosts = ["192.168.1.100", SnmpSession(DestHost="example.com", Community="mysecret"),
"example.net", "example.org"]
运行作业 worker 将获得两个串形式主机名和完整 SnmpSession 对象
结束语
官方文档和多进程库样有用您应当特别关注其中提到以下这些事项:避免共享状态;最好显式地连接所创建进程;尽量避免终止具有共享状态进程;最后确保在连接前删除队列中所有队列项否则将出现死锁官方文档中提供了有关最佳实战更多详细信息因此建议您阅读 参考资料 小节中编程资源指南
除了以上注意事项的外多进程也是 Python 编程语言大增强尽管 GIL 对线程限制曾经被认为是个弱点但是通过包含强大灵活多进程库Python 不仅弥补了这个弱点而且还得到了增强非常感谢 David Goodger 担任本文技术审校!
最新评论