Skip to main content
 Web开发网 » 编程语言 » Python语言

Python中的多线程

2021年11月28日5780百度已收录

回顾在Python进阶记录之基础篇(二十三)中,我们介绍了进程的基本概念以及Python中多进程的基本使用方法。其中,需要重点掌握多进程的创建方法、进程池和进程间的通信。今天我们讲一下Python中的多线程。

线程的基本概念线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以拥有多个并发的执行线索,这些执行线索也被称为可以获得CPU调度的执行单元,这就是所谓的线程。

由于线程在同一个进程下,因此它们可以共享相同的上下文。相对于进程而言,线程间的信息共享和通信也就更加容易。

当然在单核CPU系统中,真正的线程并发是不可能的,因为在某个时刻能够获得CPU的只有唯一的一个线程,多个线程共享了CPU的执行时间。

Python中的多线程我们在之前内容中提到过,Python实现并发编程有三种方式,多进程是其中的一种,而多线程也是其中的一种。Python中实现多线程有两个模块:早期的thread模块(现在名为_thread)和目前主流的threading模块。

其中,thread是Python早期版本中处理多线程的模块,过于底层,而且很多功能都没有提供,属于低级模块。因此,我们现在主要使用的是比较高级的threading模块,threading模块对thread模块进行了封装,尤其是对多线程编程提供了更好的面向对象的封装。

现在,我们把上一节内容中下载文件的例子用多线程的方式来实现并发下载。

Python中的多线程  python多线程 第1张

多线程

上述代码中,我们直接使用threading模块的Thread类来创建线程。与多进程类似,也是传入两个属性:target目标函数,args目标函数所需参数。然后调用start( )方法启动线程,调用join( )方法等待线程结束继续执行。

我们说过,threading模块对多线程编程提供了更好的面向对象的封装。因此,对于线程的创建,我们通常是定义一个继承自Thread类的子类。

Python中的多线程  python多线程 第2张

面向对象的多线程

我们首先定义一个类,让它继承自Thread,然后重写Thread类中的run( )方法。当类的实例对象调用start( )方法时,就会触发这个run( )方法,因此我们会把线程要处理的代码放在run( )方法中。

线程锁Lock多线程和多进程最大的不同在于,在多进程中,同一个变量,每个进程都各自有一份拷贝,彼此之间互不影响,而在多线程中,每个变量都由所有线程共享。因此,任何一个变量都可以被任何一个线程修改。

我们来看这样一个例子:将一个共享变量封装成一个类,然后自定义一个线程类,执行的功能是给共享变量加1,我们创建100个线程,并发执行,来看看结果。

Python中的多线程  python多线程 第3张

多线程同时操作变量

运行上述代码我们发现,运行结果并不是我们预计的100,而是比100要小得多。之所以出现这种情况,是因为多个线程并发执行时,有可能会一起执行到new_data = self.__data + change这行代码,而每个线程得到的共享变量初始值都是self.__data = 0,都是在0上面做了加1的操作。

举个例子,现在线程1和线程2同时执行到加1操作,但由于两个线程得到的初始数据都是0,因此从结果上来看,总共只加了1。这就是为什么我们最后得到的是错误的结果。

线程之间共享数据虽然简单,但最大的危险就在于多个线程同时改一个变量,会把内容改乱。如果我们要确保上述共享变量data的正确性,就需要在change_data( )中上一把锁,这就是所谓的线程锁。

当某个线程开始执行change_data( )方法时,我们说,该线程此时获得线程锁,那么其他线程将无法同时执行change_data( )方法,只能等待当前线程释放线程锁后,才能获得线程锁进行修改。由于线程锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。

Python中创建线程锁通过threading模块的Lock( )方法来实现。

Python中的多线程  python多线程 第4张

线程锁Lock

当多个线程同时执行到lock.acquire( )方法时,只有一个线程能成功地获取线程锁,然后继续执行代码,其他线程就只能继续等待直到获得线程锁为止。获得线程锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。因此我们使用try...finally来确保线程锁一定会被释放。可以看到,当我们加上线程锁后,运行结果就正确了。

线程锁的好处是确保了某段关键代码只能由一个线程从头到尾完整地执行,从而避免共享数据混乱。当然,这样做也是有代价的,首先是阻止了多线程的并发执行,包含线程锁的某段代码实际上只能以单线程模式执行,我们在执行代码的过程中就能感受到,加了线程锁的程序执行时间会明显增加,效率就大大地下降了。其次,当使用多个线程锁时,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

此外,Python的多线程无法发挥CPU的多核特性,因为Python的解释器有一个“全局解释器锁”的东西,我们称之为GIL锁,任何线程执行前必须先获得GIL锁。每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,使我们看上去像是在一起执行一样。不过即使如此,在Python中使用多线程依然能提升执行效率。

总结以上内容介绍了线程的基本概念以及Python中多线程的使用方法,需要重点掌握多线程的创建和使用、理解线程锁的意义并能正确使用它。最后,我们总结一下Python实现并发编程的三种方式:多进程、多线程、多进程+多线程。感谢大家的支持与关注,欢迎一起学习交流~

评论列表暂无评论
发表评论
微信