并发和并行 | Python中实现多线程 threading 和多进程 multiprocessing

并发和并行 | Python中实现多线程 threading 和多进程 multiprocessing

昨天晚上组会轮到我汇报技术内容,最近正在和 ray 以及 spark 打交道,索性讲一下并发和并行。反正你们都是管理学院的,平时不多接触这种,所以这个选题不大可能由于内容基础而贻笑大方。html

本文摆一摆并发和并行。附上很简单的 Python 代码,涉及到自带库 threadingmultiprocessing 的使用。python

并发和并行

我们简单用多线程对应并发,多进程对应并行。多线程并发更强调充分利用性能;多进程并行更强调提高性能上限。bash

我用很是简单且不那么严谨的比喻来讲明。微信

多线程

一个 CPU 至关于一个学生。网络

一个学生一周开一次组会,换句话说一周给老师汇报一次工做。多线程

老师通常会给学生同时布置几个任务,好比作比赛、作项目、读论文,学生可能周一作作比赛、周二读读论文、周三作作项目... 到了组会,他就把三件事都拿出来汇报,老师很欣慰,由于在老师的视角里:学生这三件事是同时在作的。并发

多线程也是同一个道理,假设你的手机只有一块单核 CPU 。你的 CPU 这 0.01 秒用来播放音乐,下 0.01 秒用来解析网页... 在你的视角里:播放音乐和解析网页是同时进行的。你大能够畅快地边听音乐边网上冲浪异步

何谓充分利用性能? 若是这学生只有一项工做,那他这一周可能只须要花费两天来作任务,剩下时间摸鱼(针不搓,三点钟饮茶先!)。所以,咱们用「多线程」来让学生实现『并发』,充分利用学生能力。分布式

giphy.com

在实际状况中,多线程、高并发这些词语更多地出如今服务端程序里。好比一个网络链接由一个线程负责,一块 CPU 能够负责处理多个异步的请求,大大提高了 CPU 利用率。函数

多进程

多个 CPU ( CPU 的多核)至关于多个学生。

一个任务能够拆成几个任务相互协做、同时进行,则是多进程。

好比研究生课程,老师非得留个论文做业,都研究生了我去,留啥大做业。

那咱就多线程并行搞呗。肯定了大概思路,剩下的一股脑写就行。咱队伍里一共甲乙丙丁四名同窗,那就:

  • 甲同窗负责 Introduction
  • 乙同窗负责 Background
  • 丙同窗负责 Related Works
  • 丁同窗负责 Methodology

这是乙同窗提出异议:不该该是先完成 Introduction 再写 Background ,一个个来嘛?

大哥,都研究生了嗷,做业糊弄糊弄差很少得了啊。让你写你就写。

能够预知,上述四部分同时进行,怎么也比一我的写四块要快。

giphy.com

因此说 多进程并行提高性能上限

在实际状况中,多进程更多地与高性能计算、分布式计算联系在一块儿。

Python 实现

首先声明咱的实验环境。

> python --version
Python 3.8.5

我们设置个任务:求数的欧拉函数值。

def euler_func(n: int) -> int:
    res = n
    i = 2
    while i <= n // i:
        if n % i == 0:
            res = res // i * (i - 1)
            while (n % i == 0): n = n // i
        i += 1
    if n > 1:
        res = res // n * (n - 1)
    return res

求一个数的欧拉函数值可能很快,可是一堆数呢?

因此咱想着用并行完成这个任务。

我们把任务分红三份。

task1 = list(range(2, 50000, 3))  # 2, 5, ...
task2 = list(range(3, 50000, 3))  # 3, 6, ...
task3 = list(range(4, 50000, 3))  # 4, 7, ...

def job(task: List):
    for t in task:
        euler_func(t)

来看看平平无奇的正常串行。

@timer
def normal():
    job(task1)
    job(task2)
    job(task3)

完成了 task1 再完成 task2 ... 行,没毛病。

看看多线程?

import threading as th

@timer
def mutlthread():
    th1 = th.Thread(target=job, args=(task1, ))
    th2 = th.Thread(target=job, args=(task2, ))
    th3 = th.Thread(target=job, args=(task3, ))

    th1.start()
    th2.start()
    th3.start()

    th1.join()
    th2.join()
    th3.join()

再看看多进程?

import multiprocessing as mp

@timer
def multcore():
    p1 = mp.Process(target=job, args=(task1, ))
    p2 = mp.Process(target=job, args=(task2, ))
    p3 = mp.Process(target=job, args=(task3, ))

    p1.start()
    p2.start()
    p3.start()

    p1.join()
    p2.join()
    p3.join()

上述代码的逻辑是这样的:

  • 我建立线程/进程,其生来的目的就是完成任务job(task1)job(task2)job(task3),注意这里函数名和参数被分开了target=job, args=(task1, )
  • 而后 start() ,告诉线程/进程:你能够开始干活了
  • 他们本身干本身的,我们程序主逻辑还得继续往下运行
  • join() 这里,我们是指让线程/进程阻塞住咱的主逻辑,好比p1.join()是指:p1不干完活,我主逻辑不往下进行(属因而「阻塞」)
  • 这样,咱们的函数multcore结束后,必定其中的线程/进程任务都完成了

咱看看结果:

if __name__ == '__main__':

    print("同步串行:")
    normal()

    print("多线程并发:")
    mutlthread()

    print("多进程并行:")
    multcore()

# 下面是结果
同步串行:
timer: using 0.24116 s
多线程并发:
timer: using 0.24688 s
多进程并行:
timer: using 0.13791 s

结果不太对,按理说,多进程并行的耗时应该是同步串行的三分之一,毕竟三个同等体量的任务在同时进行。

多线程并发同步串行慢是应该的,由于多线程并发同步串行的算力是同样的,可是多线程并发得在各个任务间来回切换,致使更慢。

你问 @timer 是什么意思?哦,这个是我写的修饰器,以下。

def timer(func):
    @wraps(func)
    def inner_func():
        t = time.time()
        rts = func()
        print(f"timer: using {time.time() - t :.5f} s")
        return rts
    return inner_func

不太明白『Python修饰器』的老铁,不如给我点个「在看」,再关注下我,我们之后详细道来。

我是小拍,微信 PiperLHJ ,感谢关注与在看。

相关文章
相关标签/搜索