如何提高python效率?——并发编程介绍

2022-12-19 15:55:19 浏览数 (2428)

对于计算机而言,提高效率的方式其实是比较有限的,因为除非从底层代码(汇编代码)进行优化,否则想要提升效率是比较困难的,只能通过不断的优化代码和算法,提高效率。但实际上还有另外的方法也可以提高效率,来看看python有哪些提高效率的方法:

基础概念介绍

进程:进程是一个程序的执行实例,他是一个动态的概念,是操作系统进行资源分配的基本(最小)单位,一个进程中包含了程序执行过程中的所有资源。进程之间数据交换需要使用到中间件。

线程:线程是CPU的最小调度单位,同一个进程里面的线程共享所有资源(没错,一个进程里可以有多个线程,每个线程都运行在同一进程的上下文中,共享同样的代码和全局数据,所以线程间的数据交换会来得容易些)。

协程:由于python存在GIL锁,似的python的多线程没有太多意义,这时候出现了自己操作线程的切换,以降低线程切换的开销,这就是协程。

 通俗理解线程进程的关系:

进程是连锁店,线程是店里的灶台

一个进程可以有多个线程,——》一个连锁店可以有多个灶台

进程间不能直接通信——》连锁店之间通信是不方便的

线程间共享所有支援,可以直接通信——》灶台之间共享店内的所有食材
进程要比线程消耗更多的计算机资源。——》开个店比开个灶台更贵

提升效率的方法一——多进程

在上文的类比中,我们可以知道进程是连锁店,为了赚大钱(提高效率),我们可以多开几家店,这就是多进程技术。

在python中多进程使用multiprocessing库来实现。示例如下所示:

# import 多进程库
import multiprocessing

def worker1(name):
    print("worker1 name is " + name)

def worker2(name):
    print('worker2 name is' + name )


if __name__ == "__main__":
    # target后面传入要多进程的方法,args以元组的方式传入参数
    # 创建两个进程分别调用不同的方法
    p1 = multiprocessing.Process(target=worker1, args=('subprocess1',)) 
    p2 = multiprocessing.Process(target=worker2, args=('subprocess2'))

    #启动进程
    p1.start()
    p2.start()
    
    #停止进程
    p1.join()  
    p2.join()

还记得上文提到的嘛?多进程间进程通信是比较困难的,但这并不代表没有通信的手段,为了实现多进程间的通信,我们可以使用队列(Queue)来实现,它是一种多进程安全的队列,有关他的更多用法可以查看python3教程中的相关文档。

 为什么要使用队列来进行通信?很简单,因为如果没有通信,进程之间就无法协作,会出现冲突,就像开连锁店一样,如果没有协调好每个店的经营内容,可能会出现互相抢客户的现象。

提升效率的方法二——多线程

多线程技术在其他语言中是可以正常使用的,但在python中有例外,因为python存在一个GIL锁,它规定了线程在运行时,需要先拿到通行证,否则就不能运行,也就意味着一个python的进程里,无论你有多少个线程,永远只能单线程运行。

还记得上文说过,线程是cpu最小调度单位吗?也就意味着,python多线程是无法使用多核的,但是多进程是可以利用多核的。

 怎么理解GIL锁呢,其实就是相当于只有一个大师傅,虽然你有很多灶台,但是你只有一个人可以做菜。

那么python的多线程是不是没用呢?不是,如果你的程序是CPU密集型的,那么python的多线程是完全没有意义,甚至由于线程切换的花销,会导致更慢点。

但如果你的是IO密集型,那么多线程的提升还是很明显的。

 io密集型,就是读取数据比较耗费时间,而cpu处理时间比较短,程序花费的空闲的时间主要是cpu在等待io,这就是io密集型,比如等待网络数据,文件读写等

CPU密集型,就是处理数据比较耗费时间,读写不耗费时间,程序花费的时间主要是cpu在处理数据,而只有一小段时间是用在io上,这就是cpu密集型,比如算法运算,复杂逻辑处理等等。

以大师傅为例,io密集型说的就是食材的烹饪前处理、端菜上桌比较耗费时间,cpu密集型说的就是食材做成才比较耗费时间。

虽然大师傅可以有很多个灶台,但大师傅只有一个,cpu密集型的程序就相当于大师傅要一直做菜,还要从一个灶台跑到另一个灶台,大师傅会很累,而且同一时间内大师傅只能在一个灶台上做菜,所以实际上也没有更快,因为大师傅还要跑来跑去,反而更慢了
io密集型的程序就相当于大师傅做刺身(很简单的料理,但是提前处理材料很麻烦),每个灶台都有自动处理机器,它可以自动把食材处理好,只要大师傅到位就可以做菜,做完也可以不用管,马上切换到别的灶台继续做。所以多线程对于io密集型的程序提升确实是比较明显的。

python的多线程使用的是threading库(其实还有thread库,但这个库比较简单,不推荐),示例如下所示:

# import 线程库
import threading

# 这个函数名可随便定义
def run(n):
    print("current task:", n)

if __name__ == "__main__":
    # 创建线程
    t1 = threading.Thread(target=run, args=("thread 1",))
    t2 = threading.Thread(target=run, args=("thread 2",))
    t1.start()
    t2.start()

协程介绍

协程是一种操作,原来多线程是由CPU控制的,而协程则是自己控制。当代码中出现有io处理的时候,先代码自行调度,将这个操作挂起,然后去继续执行其他操作。

这样的话,cpu就不会因为代码中出现io处理进行线程切换,从而减少线程切换的花销,提升运行速度。

 大师傅在做菜的时候可能需要蒸十五分钟,这十五分钟大师傅完全可以去干别的,按照原来的多线程,大师傅得把这个灶头的菜坐完再切换到别的灶头,而协程的出现则改变了这个情况,大师傅发现蒸菜十五分钟,他就去别的灶台干别的活了,等到蒸好了再切换回来。

python的协程使用的asyncio库,示例代码如下所示:

import asyncio
 # 需要利用队列来进行协程之间的数据交换
queue =  asyncio.Queue()

async def Producer():
        n = 0
        while True:
            await asyncio.sleep(2)
            print('add value to queue:',str(n))
            await queue.put(n)
            n = n + 1

async def Consumer():
    while True:
        try:
            r = await  asyncio.wait_for(queue.get(), timeout=1.0)
            print('consumer value>>>>>>>>>>>>>>>>>>', r)
        except asyncio.TimeoutError:
            print('get value timeout')
            continue
        except:
            break
    print('quit')
loop = asyncio.get_event_loop()
tasks = [Producer(), Consumer()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

协程跟进程、线程的区别

  1. 协程既不是进程也不是线程,协程仅仅是一个特殊的函数,协程它进程和进程不是一个维度的。
  2. 一个进程可以包含多个线程,一个线程可以包含多个协程。
  3. 一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。
  4. 协程与进程一样,切换是存在上下文切换问题的。

小结

以上就是有关于python并发编程的简单介绍了,想要更多了解python的并发编程,可以前往裴帅帅老师的新课程——Python 多线程多进程多协程 并发编程实战进行学习!

Python 多线程多进程多协程 并发编程实战