由于GIL的存在,python中的多线程其实并不是真正意义上的多线程,前面一篇文章
Python中的多线程与多进程那些事
中提到
I/O密集型使用多线程并发执行提高效率、计算密集型使用多进程并行执行提高效率。
针对计算密集型的任务,我们如何通过多进程提高效率?
Window下的multiprocessing分布式计算
基于multiprocessing、queue等模块实现简易的分布式计算框架。服务节点负责任务的派发和任务结果的采集,工作节点分布在同一电脑的不同进程,或者其他电脑上,负责任务执行和结果反馈,服务节点与工作节点通过Queue实现数据共享(任务下发、结果反馈)。
其中包含
Task、MaterWork、SlaveWork三个模块,大体介绍及源码如下:
Task模块
根据任务需求,将自己的任务单独封装在task模块中。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Task:
def __init__(self, job_id):
self.task_id = job_id
def computer(self):
return self.task_id*self.task_id
MaterWork(主节点/服务节点)
负责任务派发和结果采集,主从节点通过Queue实现任务、结果共享。代码中注释中已介绍的较清楚,这里不做赘述。
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import Queue
from multiprocessing.managers import BaseManager
from Task import Task
#window下运行防止多进程可能崩溃
from multiprocessing import freeze_support
class QueueManager(BaseManager):
pass
# 发送任务的队列
task_queue = Queue.Queue()
# 接收结果的队列
result_queue = Queue.Queue()
# 解决__main__. not found问题
def get_task_queue():
return task_queue
# 解决__main__. not found问题
def get_result_queue():
return result_queue
def Master_Work():
# 把两个queue注册到网络上
QueueManager.register('get_dispatched_job_queue', callable=get_task_queue)
QueueManager.register('get_finished_job_queue', callable=get_result_queue)
# 绑定端口9527,设置验证码123456
manager = QueueManager(address=('192.168.48.89', 9527), authkey='123456')
manager.start()
# 通过网络访问Queue对象
dispatched_jobs = manager.get_dispatched_job_queue()
finished_jobs = manager.get_finished_job_queue()
# 添加待处理任务,实际应用过程添加的任务可以为函数的参数
for i in range(0, 10):
task = Task(i).computer()
print 'Dispatch job: {0}'.format(i)
dispatched_jobs.put(task)
# 列队任务消费完,开始获取任务执行结果
while not dispatched_jobs.empty():
for i in range(0, 10):
TaskResult = finished_jobs.get(60)
print 'Finished Job {0}'.format(TaskResult)
# 关闭服务
manager.shutdown()
if __name__ == '__main__':
freeze_support()
Master_Work()
SlaveWork(从节点/计算节点)
负责任务执行和结果反馈,主从节点通过Queue实现任务、结果共享。
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import Queue
from multiprocessing.managers import BaseManager
import time
class QueueManager(BaseManager):
pass
# 从网络上获取Queue
QueueManager.register('get_dispatched_job_queue')
QueueManager.register('get_finished_job_queue')
# 连接服务器
server_addr = '192.168.48.89'
print 'Connect to server {0}'.format(server_addr)
manager = QueueManager(address=(server_addr, 9527), authkey='123456')
manager.connect()
# 获取Queue对象
dispatched_jobs = manager.get_dispatched_job_queue()
finished_jobs = manager.get_finished_job_queue()
# 消费队列执行任务
while not dispatched_jobs.empty():
try:
#任务函数 此处可添加任务函数
task = dispatched_jobs.get(timeout=1)
print('SlaveWork1 Run job: {0}'.format(task) )
finished_jobs.put(task)
except Queue.Empty:
print 'task queue is empty'
print 'worker exit!'
使用说明
3.1 在Task.py 添加任务函数。
一个小栗子
3.2 调整主从节点任务派发具体函数、次数或参数。
一般情况分发次数等于任务数,即分发任务的参数。
# 添加待处理任务,实际应用过程添加的任务可以为函数的参数
for i in range(0, 10):
task = Task(i).computer()
print 'Dispatch job: {0}'.format(i)
dispatched_jobs.put(task)
3.3 启动主节点
3.3 启动从节点
将从节点计算模块发送至不同电脑,启动从节点即实现分布式计算,即N个从节点消费服务节点分发任务,并返回任务执行状态、结果。
3.4 启动从节点后,可以在主节点运行界面,看到任务完成情况。