多进程:使用 Python 实现多进程

  介绍

  在 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 担任本文技术审校!



Tags:  linux多进程 apache多进程设置 多进程多线程 多进程

延伸阅读

最新评论

发表评论