在学完第9章Selenium Grid之后,我们了解到Selenium Grid虽然可以分布式执行测试用例,但它并不支持并行。“分布式”和“并行”是两个完全不同的概念,分布式只负责将一个测试用例远程调用到不同的环境下执行;而并行强调“同时”执行多个任务。如何实现并行呢?可以利用编程语言提供的多线程(或多进程)技术来实现并行。
本章将学习Python的多线程与多进程技术,并将其应用到自动化测试用例的执行中。
在使用多线程之前,我们首先要理解进程和线程的概念。
什么是进程?
计算机程序只不过是磁盘中可执行的二进制(或其他类型)数据。它们只有在被读取到内存中、被操作系统调用的时候才开始它们的生命周期。进程是程序的一次执行,每个进程都有自己的地址空间、内存、数据栈,以及其他记录其运行轨迹的辅助数据。操作系统管理在其上面运行的所有进程,并为这些进程公平地分配时间。
什么是线程?
线程(有时被称为轻量级进程)与进程有些相似,不同的是,所有的线程都运行在同一个进程中,共享相同的运行环境。我们可以想象成是在主进程或“主线程”中并行运行的“迷你进程”。
10.1 单线程的时代
在单线程时代,当处理器需要处理多个任务时,必须对这些任务安排执行顺序,并按照这个顺序来执行任务。假如我们创建了两个任务;听音乐(music)和看电影(movie),在单线程中,我们只能按先后顺序来执行这两个任务。下面就通过一个例子来演示。
onethread.py
from time import sleep, ctime # 听音乐任务 def music(): print('I was listening to music! %s' % ctime()) sleep(2) # 看电影任务 def movie(): print('I was at the movies! %s' % ctime()) sleep(5) if __name__ == '__main__': music() movie() print('all end:', ctime())
分别创建了两个任务music和movie,执行时间分别为2秒和5秒,通过sleep()方法设置休眠时间来模拟任务的运行时间。
运行结果。
D:\Python38\python.exe F:/python_project/学习验证/onethread.py I was listening to music! Mon Sep 20 14:53:17 2021 I was at the movies! Mon Sep 20 14:53:19 2021 all end: Mon Sep 20 14:53:24 2021 Process finished with exit code 0
从运行结果可看到,程序从11分04秒开始播放music,11分06秒结束并开始movie的播放,最后,到11分11秒movie播放结束,总耗时7秒。
现在。我们对上面的例子做些调整,使它看起来更加有意思。
首先music和movie作为播放器,在用户使用时,可以根据用户的需求来播放任意的歌曲和影片,并且我们希望播放器能够提供循环播放的功能,尤其对于音乐播放器来说这个很重要,改造后的程序如下。
onethread2.py
from time import sleep, ctime # 音乐播放器 def music(func, loop): for i in range(loop): print('I was listening to %s! %s' % (func, ctime())) sleep(2) # 视频播放器 def movie(func, loop): for i in range(loop): print('I was at the %s! %s' % (func, ctime())) sleep(5) if __name__ == '__main__': music('爱情买卖', 2) movie('阿凡达', 2) print('all end:', ctime())
给music()和movie()两个函数设置参数:播放文件和播放次数。而函数中通过for循环控制播放的次数。再次运行,结果如下。
D:\Python38\python.exe F:/python_project/学习验证/onethread2.py I was listening to 爱情买卖! Mon Sep 20 14:56:34 2021 I was listening to 爱情买卖! Mon Sep 20 14:56:36 2021 I was at the 阿凡达! Mon Sep 20 14:56:38 2021 I was at the 阿凡达! Mon Sep 20 14:56:43 2021 all end: Mon Sep 20 14:56:48 2021 Process finished with exit code 0
从运行结果可以看到,程序从42分36秒开始播放music,42分40秒music两轮播放结束并开始播放movie;42分50秒两个任务结束,最终总耗时14秒。
10.2 多线程技术
Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。threading基于Java的线程模型设计。锁(Lock)和条件变量(Condition)在Java中是对象的基本行为(每一个对象都自带了锁和条件变量),而在Python中则是独立的对象。
10.2.1 threading模块
我们应该避免使用thread模块,原因是它不支持守护线程。当主线程退出时,所有的子线程不管它们是否还在工作,都会被强行退出。有时我们并不希望发生这种行为,这时就引入了守护线程的概念。threading模块支持守护线程,所以,我们直接使用threading来改进上面的例子。
threads.py
from time import sleep, ctime import threading # 音乐播放器 def music(func, loop): for i in range(loop): print("I was listening to %s! %s" % (func, ctime())) sleep(2) # 视频播放器 def movie(func, loop): for i in range(loop): print("I was at the %s! %s" % (func, ctime())) sleep(5) # 创建线程数组 threads = [] # 创建线程t1,并添加到线程数组 t1 = threading.Thread(target=music, args=('爱情买卖',2)) threads.append(t1) # 创建线程t2,并添加到线程数组 t2 = threading.Thread(target=movie, args=('阿凡达', 2)) threads.append(t2) if __name__ == '__main__': # 启动线程 for t in threads: t.start() # 守护线程 for t in threads: t.join() print('all end: %s' % ctime())
通过for循环遍历threads数组中所装载的线程;start()开始线程活动,join()等待线程终止。如果不使用join()方法对每个线程做等待终止,那么在线程运行的过程中可能会去执行最后的打印“all end:..”。
class threading.Thread()方法说明:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
This constructor should always be called with keyword arguments. Arguments are:
group should be None; reserved for future extension when a ThreadGroup class is implemented.
target is t he callable obj ect t o be invoked by t he run() m ethod. Defaults to None, meaning nothing is called.
name is the thread name. By default, a unique name is constructed of the form “Thread-N” where N is a small decimal number.
args is the argument tuple for the target invocation. Defaults to ().
kwargs is a dictionary of keyword arguments for the target invocation. Defaults to {}.
If the subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread.
运行结果。
D:\Python38\python.exe F:/python_project/学习验证/threads.py I was listening to 爱情买卖! Mon Sep 20 14:28:52 2021 I was at the 阿凡达! Mon Sep 20 14:28:52 2021 I was listening to 爱情买卖! Mon Sep 20 14:28:54 2021 I was at the 阿凡达! Mon Sep 20 14:28:57 2021 all end: Mon Sep 20 14:29:02 2021 Process finished with exit code 0
从上面的运行结果可以看出,两个子线程(music、movie)同时启动于11分24秒,直到所有线程结束于11分34秒,总耗时10秒。movie的两次电影循环共需要10秒,music的歌曲循环需要4秒,从执行结果可以看出两个线程达到了并行工作。