带装饰器的Python中的简化多进程、多线程并发(装饰并发-Python多线程、进程神器)

多线程、多进程、协程的基本原理概念、以及Python中的基本实现方法。请看笔者之前的写的文章。
传送门-Python中理解进程(Process),线程(Thread)和协程(Coroutines)的感悟

威斯康星大学麦迪逊分校的Alex Sherman和Peter Den Hartog编写了一个新的有趣的Python多处理程序包装,称为deco。

这是在Python中同时运行代码的简化方法。由于CPython全局解释器锁,运行Python中的并发代码当前需要使用多个独立的进程,并对它们的函数调用和参数进行序列化和管道化。

deco
论文paper: DECO: Polishing Python Parallel Programming
代码: https://github.com/alex-sherman/deco
基本使用和安装教程请看Readme.md

该库基于称为Pydron的东西,2016年时有人称这个项目是一项研究,当时并未没有发布任何代码。

除了在函数上使用简单的装饰器之外,最大的不同在于deco,它真的很容易上手,并且对如何收集子流程调用的结果也有严格的限制。

在deco中,您传入具有键索引(例如python )的可变对象dict。python list 也是可变的,但没有索引。意思是,您可以获取处理信息通过mylist.append()。

“但是,DECO确实对该程序施加了一个重要限制:所有突变都只能基于索引。”

先来看一个基本示例了解deco如何工作:

@concurrent   #Identify the concurrent function
def do_work(key):
  return some_calculations(...)

data = {}
@synchronized
def run():
  for key in data:
    data[key] = do_work(key)
  print data # data will be automatically synchronized here

程序员唯一干预就是插入两个装饰器@concurrent和@synchronized。在@concurrent你想一个函数来并行运行标识及@synchronized装饰标记的功能,其中并发功能将被调用。

为什么说deco强大,真正的理由是这样的:

我们可以修改并发函数中的参数,并且修改的内容同步回父进程。这不同于Python的multiprocess.pool,后者要求从并发函数中返回任何已更改的状态,并丢弃对参数的修改。例如,您可以拥有一个经度范围的字典,并调用一个@concurrent函数,该函数计算给定范围内的平均温度并更新该范围的键。只要您在循环中调用该函数,并且每次调用该函数都更新字典中的唯一键,您的计算就将并行进行,并且在访问父级中的数据之前,将自动同步各个过程的结果处理(@synchronized 装饰者可以处理)。

如上面的示例所示,您可以将并发函数调用的结果分配给索引或键对象(列表和字典),并在并行调用完成后以及在父进程中访问数据之前将它们同步到这些位置。再次,@synchronized装饰器使之成为可能,该装饰器实际上重写了父函数体内的分配,以允许它们同时发生并在以后进行同步。

实际使用多进程完整模板:

import deco
import time
import random
from collections import defaultdict


@deco.concurrent(processes=16) 
def process_lat_lon(lat, lon, data):
    """
    We add this for the concurrent function
    :param lat: 
    :param lon: 
    :param data: 
    :return: 
    """
    time.sleep(0.1)
    return data[lat + lon]


@deco.synchronized  
def process_data_set(data):
    """
    And we add this for the function which calls the concurrent function
    :param data: 
    :return: 
    """
    results = defaultdict(dict)
    for lat in range(10):
        for lon in range(10):
            results[lat][lon] = process_lat_lon(lat, lon, data)
    return dict(results)


if __name__ == "__main__":
    """Windows下必须在__main__命名下,所以如果调用,也都遵循这个规则吧"""
    random.seed(0)
    data = [random.random() for _ in range(200)]
    start = time.time()
    print(process_data_set(data))
    print(time.time() - start)

实际使用多线程完整模板:

import deco
import time
import random
from collections import defaultdict


@deco.concurrent.threaded(processes=16)
def process_lat_lon(lat, lon, data):
    """
    We add this for the concurrent function
    :param lat:
    :param lon:
    :param data:
    :return:
    """
    time.sleep(0.1)
    return data[lat + lon]


@deco.synchronized
def process_data_set(data):
    """
    And we add this for the function which calls the concurrent function
    :param data:
    :return:
    """
    results = defaultdict(dict)
    for lat in range(10):
        for lon in range(10):
            results[lat][lon] = process_lat_lon(lat, lon, data)
    return dict(results)


if __name__ == "__main__":
    """Windows下必须在__main__命名下,所以如果调用,也都遵循这个规则吧"""
    random.seed(0)
    data = [random.random() for _ in range(200)]
    start = time.time()
    print(process_data_set(data))
    print(time.time() - start)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章