自动化测试提速必备 - 并发编程

在实际的自动化测试中,我们经常碰到类似的需求:

在这个时候往往我们需要使用并发编程的技术-多线程和多进程,通过并发执行测试用例,可以显著缩短自动化测试时间,同时也能实现在多个环境中同时执行测试的效果。

那么到底什么是多进程和多线程,以及什么时候选择多进程什么时候选择多线程呢?

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中可以创建多个线程,但在任何给定时刻只有一个线程能够真正执行代码,其他线程会被阻塞。

image.png

我们可以根据上述知道:即便我们的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密集型任务选择多线程。

回帖
请输入回帖内容 ...