zoukankan      html  css  js  c++  java
  • Python并行计算专题

    最近工作中经常涉及到Python并行计算的内容,所以今天做一期专题的知识整理。本文将涉及以下三块内容:1. 多核/多线程/多进程的概念区分,2. Python多线程,多进程的使用方式,3. Python进程池的管理方案

    多核/多线程/多进程

    一般而言,计算机可以并行执行的线程数量就是CPU的物理核数,因为CPU只能看到线程(线程是CPU调度分配的最小单位)。然而,由于超线程技术的存在,现在的CPU可以在每个内核上运行更多的线程,因此,现代计算机可以并行执行的线程数量往往是CPU物理核数的两倍或更多

    进程是操作系统资源分配(内存,显卡,磁盘等)的最小单位,线程是执行调度(即CPU资源调度)的最小单位(CPU看到的都是线程而不是进程)。一个进程可以有一个或多个线程,线程之间共享进程的资源。如果计算机有多个内核,且计算机中的总的线程数量小于逻辑核数,那线程就可以并行运行在不同的内核中。如果是单核多线程,那多线程之间就不是并行的关系,它们只是通过分时的方式,轮流使用单个内核的计算资源,营造出一种“并发”执行的假象。由于线程切换需要耗费额外的资源,因此如果多线程协同处理的是同一个计算任务,那么该任务的完成速度是不如单线程从头算到尾的

    针对计算密集型的任务,我们使用与逻辑内核数相同的线程数就可以最大化地利用计算资源(线程数少了会引起内核资源的闲置,线程数多了则会消耗不必要的资源在分时切换上)。如果是IO密集型任务,我们就需要创建更多的线程,因为当一个线程已经算好结果,在等待IO写入时,我们就可以让另一个线程去使用此时空闲的内核资源,在这个场景下线程间切换的代价是小于内核资源闲置的代价的

    在上一段中,我们讨论的一直都是线程,那么什么时候我们应该使用更多的进程呢?回顾之前提到过的进程的特点:

    进程是操作系统资源分配的最小单位

    因此,是否使用多进程,这取决于你是否想要,并且是否能够发挥某一系统资源IO性能的最大值。举个例子:比如你想要尽可能快地往磁盘中写入数据,而此时一个进程并不能占满磁盘IO的带宽,那么我们就可以使用多进程,并发地往磁盘中写入内容,进而最大化地发挥磁盘的读写性能

    一般而言,只要CPU 有多个逻辑内核,那么多个线程就能够在不同的内核上并发执行(即使此时只有一个进程)。但对 Python 来说,无论是单核还是多核,一个进程同时只能有一个线程在执行。为什么会出现这种情况呢?因为Python 在设计时采用了一种叫 GIL 的机制。GIL 的全称为 Global Interpreter Lock (全局解释器锁),它出于安全考虑,限制了每个Python进程下的线程,使其只有拿到GIL后,才能使用内核资源继续工作。因此,Python的多线程一般只用来实现分时的“并发”,要想充分发挥硬件性能,还是需要使用多个进程

    Python多线程和多进程的使用方法

    多线程

    我们先来看Python多线程的一种最简单的使用方式:

    import time
    from threading import Thread
    
    def foo(content, times):
        for i in range(times):
            timestamp = time.strftime('%H:%M:%S',time.localtime(time.time()))
            print(f"{content} {i+1}  {timestamp}")
            time.sleep(1)
    
    thread_names = ('alpha', 'bata')
    repeat_times = 3
    for name in thread_names:
        th = Thread(target=foo, args=(name, repeat_times))
        th.start()
        time.sleep(0.1)
    

    输出:

    alpha 1  21:57:58
    bata 1  21:57:58
    alpha 2  21:57:59
    bata 2  21:57:59
    alpha 3  21:58:00
    bata 3  21:58:00
    

    Python通过标准库threading提供对线程的支持,其常用方法包括:

    • threading.currentThread()——返回当前的线程变量
    • threading.enumerate()——返回一个包含正在运行的线程的list

    以及Thread对象的常用方法:

    • start():启动线程活动
    • join():阻塞当前的程序,直至等待时间结束或其Thread对象运行终止。该方法可用于计量多线程程序用时
    • isAlive():返回线程是否活动的
    • getName() 返回线程名,setName() 设置线程名

    当然,Python中的Thread不仅仅可以通过函数的方式使用,还可以以面向对象的方式使用,参考文档说明:

    The Thread class represents an activity that is run in a separate thread of control. There are two ways to specify the activity: by passing a callable object to the constructor, or by overriding the run() method in a subclass. No other methods (except for the constructor) should be overridden in a subclass. In other words, only override the __init__() and run() methods of this class.

    当我们以面向对象的方式使用Thread时,我们只需要把需要并行的部分实现至类的run()方法中,然后在外部调用线程对象的start()方法即可:

    import time
    import threading
    
    class MyThread(threading.Thread):
        
        def __init__(self, name, repeat_times):
            super(MyThread, self).__init__()
            self.content = name
            self.times = repeat_times
    
        def run(self):
            for i in range(self.times):
                timestamp = time.strftime('%H:%M:%S',time.localtime(time.time()))
                print(f"{self.content} {i+1}  {timestamp}")
                time.sleep(1)
    
    thread_names = ('alpha', 'bata')
    repeat_times = 3
    for name in thread_names:
        th = MyThread(name, repeat_times)
        th.start()
        time.sleep(0.1)
    

    该程序的输出与之前的多线程函数一致

    多进程

    Python多进程可以使用subprocess模块,参考:multiprocessing — Process-based parallelism - Python Docs

    from multiprocessing import Process
    import os
    
    # 子进程要执行的代码
    def run_proc(name):
        print('Run child process %s (%s)...' % (name, os.getpid()))
    
    if __name__=='__main__':
        print('Parent process %s.' % os.getpid())
        p = Process(target=run_proc, args=('test',))
        print('Child process will start.')
        p.start()
        p.join()
        print('Child process end.')
    

    进程池

    Python进程池的使用可参考:Python使用进程池管理进程

    from multiprocessing import Pool
    import time
    import os
    
    def action(name='http://c.biancheng.net'):
        print(name,' --当前进程:',os.getpid())
        time.sleep(3)
    
    if __name__ == '__main__':
        #创建包含 4 条进程的进程池
        pool = Pool(processes=4)
        # 将action分3次提交给进程池
        pool.apply_async(action)
        pool.apply_async(action, args=('http://c.biancheng.net/python/', ))
        pool.apply_async(action, args=('http://c.biancheng.net/java/', ))
        pool.apply_async(action, kwds={'name': 'http://c.biancheng.net/shell/'})
        pool.close()
        pool.join()
    
    

    参考:

    1. 多线程,多进程,多核总结 - 知乎 (注意此文中部分描述存在事实性错误)
    2. What Is Hyper-Threading? - Intel
    3. python多线程基础
    4. python多线程之从Thread类继承
    5. Thread Objects - Python Docs
    6. Python多线程和多进程编程(并发编程)
    7. Python使用进程池管理进程
  • 相关阅读:
    主引导扇区的理解
    敏捷的思考
    架构学习笔记
    操作系统笔记
    Docker学习笔记
    技术面试-国外人谈经验
    硬盘的原理学习
    linux压缩和解压命令总结
    好的技术团队和差的技术团队的区别在于技术架构前瞻性和适应变化的能力
    管理者的本质其实就是一个服务者,服务下属的
  • 原文地址:https://www.cnblogs.com/lokvahkoor/p/15359982.html
Copyright © 2011-2022 走看看