对于计算机而言,提高效率的方式其实是比较有限的,因为除非从底层代码(汇编代码)进行优化,否则想要提升效率是比较困难的,只能通过不断的优化代码和算法,提高效率。但实际上还有另外的方法也可以提高效率,来看看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()
协程跟进程、线程的区别
- 协程既不是进程也不是线程,协程仅仅是一个特殊的函数,协程它进程和进程不是一个维度的。
- 一个进程可以包含多个线程,一个线程可以包含多个协程。
- 一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。
- 协程与进程一样,切换是存在上下文切换问题的。
小结
以上就是有关于python并发编程的简单介绍了,想要更多了解python的并发编程,可以前往裴帅帅老师的新课程——Python 多线程多进程多协程 并发编程实战进行学习!