在实际的自动化测试中,我们经常碰到类似的需求:
- 100条自动化测试用例,采用默认的依次执行方式,所需要的时间竟然高达1个小时,我们需要想办法对其进行时间上的优化
- App自动化测试中,我们需要同时对多台测试机进行同时测试,以满足我们对于兼容性测试的需求
- 自动化测试中,我们需要额外监控应用程序/测试机的性能(比如CPU、内存的使用情况),监控不能干扰主测试流程
在这个时候往往我们需要使用并发编程的技术-多线程和多进程,通过并发执行测试用例,可以显著缩短自动化测试时间,同时也能实现在多个环境中同时执行测试的效果。
那么到底什么是多进程和多线程,以及什么时候选择多进程什么时候选择多线程呢?
Python多线程
在计算机中线程是执行中的最小单位。一个进程中可以包含多个线程,它们共享进程的资源,包括内存空间和打开的文件。每个线程都有自己的执行路径,可以独立执行任务。线程的创建和管理由操作系统负责,而不同的操作系统可能对线程有不同的实现方式。
Python提供了threading
模块来支持多线程编程。通过threading
模块,我们可以创建Thread类的实例,并传入一个函数作为线程的执行体。例如:
from threading import Thread
def worker(money):
print("开始干活")
print(f"拿到{money}块工资")
# 通过target指定线程具体执行的工作-传入对应函数,args参数执行传入函数的参数,需要注意这里是元组类型的
thread = Thread(target=worker,args=(100,))
# 启动线程
thread.start()
# 等待线程执行结束
thread.join()
多线程GIL锁的问题
我们来看以下小案例:
import time
def count(n):
while n > 0:
n -=1
start_time = time.time()
count(100000)
end_time = time.time()
print(end_time-start_time)
该程序在我的四核Intel i5 CPU上执行所需的时间为0.003968954086303711秒
把上述的程序改造成多线程模式:
import time
from threading import Thread
def count(n):
while n > 0:
n -=1
start_time = time.time()
# count(100000)
t1 = Thread(target=count, args=[100000 // 2])
t2 = Thread(target=count, args=[100000 // 2])
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()
print(end_time-start_time)
多线程模式下所需要的时间为:0.005413532257080078秒
此程序中使用了2个线程来执行和上面一样的工作,从结果来看效率没有提高反而降低了。其实这里不管使用多少线程,其结果中效率和2个线程效率几乎一样。
为什么会有这种情况?这一切的罪魁祸首来源于GIL:
全局解释器锁(GIL)是Python解释器中的一个机制,用于保护解释器内部数据结构不受多线程并发访问的影响。GIL确保了在任意时刻只有一个线程在解释器中执行Python字节码。这意味着尽管Python中可以创建多个线程,但在任何给定时刻只有一个线程能够真正执行代码,其他线程会被阻塞。
- 当线程在运行时,它会持有GIL锁,其他的进程不能持有GIL锁
- 当线程遇到IO操作时(比如读写磁盘,发送/接收网络数据等)将会释放GIL锁,其他的线程此时可以获取GIL锁
我们可以根据上述知道:即便我们的CPU是多核心,因为Python GIL锁的存在,同一时间只允许执行一个线程,单个时刻也只能使用到1个CPU核心,所以CPU的利用率不高。
多线程适用的场景
在程序运行时候有两种不同类型的计算任务:
CPU密集型计算
CPU密集型计算指的是任务主要耗费CPU资源,而对于其他资源(如内存、磁盘、网络等)的需求相对较少的计算任务。在CPU密集型计算中,程序主要执行大量的计算操作,例如数学运算、图像处理、加密解密、复杂算法等。
IO密集型计算
IO密集型计算指的是任务主要耗费在等待IO操作上,例如磁盘读写、网络通信、数据库查询等。在IO密集型计算中,CPU主要用于处理IO操作的调度和处理。
所以如果是IO密集型计算任务,使用多线程是比较合适的,虽然GIL锁存在导致只能单个线程在CPU内执行,但是在IO处理时不受限制的,IO操作较多时使用多线程依然可以加速任务的执行。
Python多进程
Python中有了多线程,为什么还需要用多进程?
前面我们讲了GIL锁的存在,当多线程遇到了CPU密集型的计算时,反而会降低程序的执行效率
GIL锁同一时间只允许执行一个线程执行,多线程之间还会涉及到上下文切换,上下文切换也需要花费时间
那么什么是多进程呢?
在操作系统中,进程是一个独立运行的程序实例。每个进程都有自己独立的内存空间,一个进程崩溃不会直接影响到其他进程。在多核处理器上,多进程能够实现真正的并行计算,每个进程可以在不同的处理器核心上运行。
Python标准库中的multiprocessing
模块能够实现多进程,这个模块提供了与多线程threading
模块类似的API。使用multiprocessing
,每个Python进程都有自己的Python解释器和内存空间,这样就避开了GIL的限制,能够充分利用多核CPU的计算资源。
from multiprocessing import Process
def worker(money):
print("开始干活")
print(f"拿到{money}块工资")
# 通过target指定进程具体执行的工作-传入对应函数,args参数执行传入函数的参数,需要注意这里是元组类型的
process = Process(target=worker,args=(100,))
# 启动进程
process.start()
# 等待子进程执行结束
process.join()
多进程的主要优势在于能够绕过GIL的限制,利用多核CPU实现真正的并行计算,特别适合处理CPU密集型任务。
其劣势在于运行一个进程消耗的资源比线程多很多,进程间的通讯也是比较复杂(比如需要用到消息队列、管道等机制)
最后总结一句话:对于CPU密集型任务选择多进程,对于IO密集型任务选择多线程。
欢迎来到testingpai.com!
注册 关于