python中的多线程和多进程

BG09

Posted by Blue Geek on September 18, 2019

python中的多线程和多进程

By 青衣极客 Blue Geek In 2019-09-19

在编写python程序时,有时会有一些并行的需求。比如在一次读取100张图片,按顺序一张张读取当然是能够完成的,只是对磁盘的IO操作的速度是比较慢的,如果使用多线程就能显著提高读取的速度。又比如在进行相互独立的100个矩阵运算的时候,如果按顺序执行会发现你的多核cpu根本没用上。这是由于python具有全局解释锁,在一个python进程中,一个时间点只能有一句代码在cpu上执行,这对于多核cpu来说实在是太浪费了。这时要加快计算的解决方案就是多进程。网上提供的一些多线程和多进程的操作虽然可行,但是在是太繁琐。本文介绍一种最简单的方式让你的python程序跑得飞起。

首先导入与python多线程和多进程之间的模块。

import os
import sys
import time
import threading
import multiprocessing
from multiprocessing import Process, Pool, Manager

1. 定义两个函数

我们在处理任务时一般需要两种函数:一种是处理完任务在函数内输出到显示器或者文件中;另一种是将计算结果回传到主线程或者主进程。本文中就以这样的两个函数来演示这两种并行需求。

# 不传回计算结果的函数
def func(i):
    # 增加一些计算量
    for j in range(10000000):
        pass
    print(i)  # 直接打印到屏幕

# 回传计算结果的函数
def func_data(i, data_dict):
    start = time.perf_counter() # 记录时间
    # 增加一些计算量
    for j in range(10000000):
        pass
    # 回填数据
    data_dict[i] = time.perf_counter() - start

2. 多线程

这里演示一种函数内输出结果的多线程使用方式。从图中输出的乱序可以看出每个线程是在并行执行的。当然还可以继承一个线程类,然后写自己的功能,但是这种方式稍显麻烦。如果业务需求并不需要那样进行的话,还是采用本文所展示的这种多线程的方式更为方便。

thread_list = []
for i in range(8):
    args = (i,)
    # 创建线程对象
    t = threading.Thread(target=func, args=args)
    t.start()
    thread_list.append(t)
# 等待所有线程执行完成
for t in thread_list:
    t.join()
1
0
3
2
6
4
5
7

3. 传回结果的多线程

在多线程回传结果时,切记不要向同一块内存进行写操作。python中的dict类型可以根据key的不同来进行写入,本文就是采用dict这个结构来回传结果的。

thread_list = []   # 用于存放线程对象
data_dict = {}     # 用于存放输出结果
for i in range(8):
    args = (i, data_dict)
    t = threading.Thread(target=func_data, args=args)
    t.start()
    thread_list.append(t)
# 等待线程运行完成
for t in thread_list:
    t.join()
# 打印多线程运行的结果
for k, v in data_dict.items():
    print('thread[{}] cost time: {}'.format(k, v))
thread[2] cost time: 1.1766515919999847
thread[0] cost time: 1.4944783349999398
thread[3] cost time: 1.549116024
thread[1] cost time: 1.5989077380000936
thread[4] cost time: 1.5176890849999154
thread[5] cost time: 1.507154589000038
thread[7] cost time: 1.417290749000017
thread[6] cost time: 1.4539079740000034

4. 多进程

线程是进程的组成部分,因此进程所需要消耗的运行资源一般高于线程。所有如果多线程能够达到目的就不要使用多进程。而在python中,如果想利用多核的计算能力来进行加速,那就非用多进程不可。

pool = Pool(processes=8)
for i in range(8):
    args = (i,)
    # 加入线程池
    pool.apply_async(func=func, args=args)
pool.close()  # 关闭进程池,不再加入新的进程
pool.join()   # 等待所有进程运行完成
1
3
0
2
4
6
5
7

5. 回传结果的多进程

由于进程之间是使用不同的逻辑地址空间的,所以内建dict这种类型无法在进程之间传递数据。而python的muiltiprocessing模块提供了一些可以在进程之间通讯的数据结构,这也为python多进程的使用提供了方便。

pool = Pool(processes=8)      # 定义进程池
data_dict = Manager().dict()  # 使用进程间通讯的词典类型
for i in range(8):
    args = (i, data_dict)
    pool.apply_async(func=func_data, args=args)
pool.close()
pool.join()
# 打印所有进程返回的结果
for k, v in data_dict.items():
    print('process[{}] cost time: {}'.format(k,v))
process[2] cost time: 0.4158330709999518
process[7] cost time: 0.41665340800000195
process[3] cost time: 0.41713137800002187
process[5] cost time: 0.4183460669999022
process[1] cost time: 0.42069877700009783
process[6] cost time: 0.4205503159998898
process[0] cost time: 0.42447853199996644
process[4] cost time: 0.4262063829999079

关于多线程与多进程的选择也是很多朋友的模糊点。这里只需要记住几个简单的原则就行:

IO密集型而不是计算密集型的,请使用多线程 计算密集型的请使用多进程 利用多核计算能力的使用多进程

CHANGELOG

  • 2020-04-07 增加解说文本

【青衣极客】公众号



COMMENT

博客评论区功能由Github Issue提供,提交Issue时请以本文标题为话题

"BG09-python中的多线程和多进程"