Python - 多线程 Parallel / Multiprocessing 示例

一.引言

Java 开发中常用到多线程和线程池提高程序运行效率和机器利用率,Python 多线程用到了 Parallel 类 和 Multiprocessing 类,除此之外还有 _thread,threading 等很多线程相关的类,可以配合 os,sys,subprocess 等工具类实现复杂的操作。下面的 Demo 通过 sum 求和的例子介绍几种多线程实现方法。

二.Parallel 无 Lock 

joblib 库下面的 Parallel 实现了并行提高效率的功能,delayed 处执行多线程要执行的函数,njobs 指定并行的 core 数,delayed 最后面的括号负责传递函数 add_sum 使用的参数。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from joblib import Parallel, delayed
import numpy as np

def add_sum(_numList):
    return sum(_numList)

if __name__ == '__main__':
    numList = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7]]
    cores = 4
    results = Parallel(n_jobs=cores)(delayed(add_sum)(num) for num in numList)
    print(sum(results))

三.MultiProcessing 无 Lock

Java 中常用到线程池,主要使用 ThreadPoolExecutor 类开发,Python 借助  multiprocessing 也可以实现线程池。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import multiprocessing


def add_sum(nums):
    return sum(nums)


if __name__ == '__main__':

    pool = multiprocessing.Pool(processes=2)
    numLists = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7]]
    sub_process = []

    for i in range(len(numLists)):
        data = numLists[i]
        process = pool.apply_async(add_sum, (data,))
        sub_process.append(process)
    pool.close()
    pool.join()

    result = 0
    for process in sub_process:
        result += process.get()
    print(result)

Tips

-> .Pool 方法可以实现线程池,processes 代表线程池的并行度
-> apply_async 实现异步执行提交,其中如果只传一个参数,需要在参数后加一个逗号,否则函数会识别错误

-> .close 关闭线程池,.join 方法等待线程池中所有任务执行完毕开始执行下面的代码,所以要避免写死循环和 Bug

-> .get 可以拿到线程处理结束后返回的结果,需要自己将多个线程的结果聚合

四.MultiProcessing 有 Lock

多线程除了并行执行外,还经常设计到同步的问题,java中通过 synchronized 加锁,控制对统一内存变量的读写, multiprocessing 通过 lock 类实现加锁解决线程同步的问题。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import multiprocessing
import time
import numpy as np
import sys


def add_sum(_initial, _numList, _lock):
    while True:
        try:
            _lock.acquire()
            value = _numList.__next__()
            _initial.value = _initial.value + value
            _lock.release()

        except StopIteration:
            _lock.release()
            sys.exit()


if __name__ == "__main__":
    lock = multiprocessing.Lock()
    initial = multiprocessing.Value('i', 0)
    work_nums = 4
    sub_process = []  # 处理数据进程集合
    numList = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7]]

    st = time.time()
    for i in range(work_nums):
        sub_process_tmp = multiprocessing.Process(target=add_sum, args=(initial, iter(numList[i]), lock))
        sub_process.append(sub_process_tmp)

    for process in sub_process:
        process.start()

    for process in sub_process:
        process.join()

    end = time.time()

    # 结果对比
    print("Add Sum Cost :", (end - st))
    realSum = np.array(numList).sum()
    print("Real Result: ", realSum, " Multi Process Result: ", initial.value)

Tips

->   lock = multiprocessing.Lock() 获得程序运行的锁,可以在线程中使用

->   initial = multiprocessing.Value('i', 0) 代表全局变量,类似于 spark 的 longAccumulator ,可以通过 .value 方法获取其值,'i' 代表类型,一般有以下类型:

Type C Type Python Type Minimum bytes
c char char 1
b signed char int 1
B unsigned char int 1
u unicode unicode char 2
h signed short int 2
H unsigned short int 2
i signed int int 2
I unsigned int long 2
l signed long int 4
L unsigned long long 4
f float float 4
d double float 8

如果共享字符串,可以执行下述代码,将 c_char_p 作为共享变量 Value 的类型指示,具体可以查看 ctypes,由于常见的是数值型累加,这里不多赘述

  from ctypes import c_char_p

->  target 为多线程执行的目标函数,args 为目标函数对应的参数,可以通过参数平均分配任务到每个线程里

-> start 启动线程,类似于 java 的 Runnable 类,join 等待线程运行结束

-> lock 有 acquire() 获取 和 release() 释放方法,同步的逻辑可以放在二者之间保证结果的一致性

-> excpet 中记得添加 lock.release() 方法,否则会导致锁无法释放程序卡死

无锁运行结果:

Add Sum Cost : 0.1731090545654297
Real Result:  64  Multi Process Result:  39

有锁运行结果:

Add Sum Cost : 0.18215608596801758
Real Result:  64  Multi Process Result:  64

无锁状态下程序运行结果不一致,可能是39,可能是正确结果 64,也可以是其他数值,加锁后数值一致,但是耗时会增加。

五.总结

通过数组求和的简单 demo,实现了有锁并行和无锁并行多种方案,可以根据自己需求修改上述代码,简单的并行用 Parallel 实现比较便捷,复杂的实现可以使用 multiprocessing,更具体的参数和实现细节可以参考官方 Api 解释。

你可能感兴趣的:(Python,Executor,常用语法,python,多线程,Parallel,multiprocessing,lock)