百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术流 > 正文

Python中的多线程(python的多线程灾难)

citgpt 2024-09-09 02:17 9 浏览 0 评论

回顾

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

线程的基本概念

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

Python中的多线程(python的多线程灾难)

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

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

Python中的多线程

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

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

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

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

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

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

线程锁Lock

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

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

运行上述代码我们发现,运行结果并不是我们预计的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( )方法来实现。

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

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

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

总结

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

相关推荐

js中arguments详解

一、简介了解arguments这个对象之前先来认识一下javascript的一些功能:其实Javascript并没有重载函数的功能,但是Arguments对象能够模拟重载。Javascrip中每个函数...

firewall-cmd 常用命令

目录firewalldzone说明firewallzone内容说明firewall-cmd常用参数firewall-cmd常用命令常用命令 回到顶部firewalldzone...

epel-release 是什么

EPEL-release(ExtraPackagesforEnterpriseLinux)是一个软件仓库,它为企业级Linux发行版(如CentOS、RHEL等)提供额外的软件包。以下是关于E...

FullGC详解  什么是 JVM 的 GC
FullGC详解 什么是 JVM 的 GC

前言:背景:一、什么是JVM的GC?JVM(JavaVirtualMachine)。JVM是Java程序的虚拟机,是一种实现Java语言的解...

2024-10-26 08:50 citgpt

使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
跨域(CrossOrigin)

1.介绍  1)跨域问题:跨域问题是在网络中,当一个网络的运行脚本(通常时JavaScript)试图访问另一个网络的资源时,如果这两个网络的端口、协议和域名不一致时就会出现跨域问题。    通俗讲...

微服务架构和分布式架构的区别

1、含义不同微服务架构:微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并...

深入理解与应用CSS clip-path 属性
深入理解与应用CSS clip-path 属性

clip-pathclip-path是什么clip-path 是一个CSS属性,允许开发者创建一个剪切区域,从而决定元素的哪些部分可见,哪些部分会被隐...

2024-10-25 11:51 citgpt

HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
Request.ServerVariables 大全

Request.ServerVariables("Url")返回服务器地址Request.ServerVariables("Path_Info")客户端提供的路...

python操作Kafka

目录一、python操作kafka1.python使用kafka生产者2.python使用kafka消费者3.使用docker中的kafka二、python操作kafka细...

Runtime.getRuntime().exec详解

Runtime.getRuntime().exec详解概述Runtime.getRuntime().exec用于调用外部可执行程序或系统命令,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。...

promise.all详解 promise.all是干什么的
promise.all详解 promise.all是干什么的

promise.all详解promise.all中所有的请求成功了,走.then(),在.then()中能得到一个数组,数组中是每个请求resolve抛出的结果...

2024-10-24 16:21 citgpt

Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解

取消回复欢迎 发表评论: