python 多线程,CSDN Python 学习班,整理自微信文字课程

2023-10-31 15:20

本文主要是介绍python 多线程,CSDN Python 学习班,整理自微信文字课程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Python作为一门优秀的编程语言,在TIOBE排行榜上,长期稳坐前十的位置。但在日常生活中,尤其是对编程小白来说,想学习一门新语言并不简单,有的甚至只能自学到一点皮毛。

为此,CSDN特向广大Python爱好者开设了学习班,帮助大家在学习Python的道路上少走弯路,事半功倍。上周五,“CSDN博客专家会客厅”走进班级,CSDN博客专家、技术达人刘冬作为受邀专家与大家畅聊Python。

我也要加入CSDN Python学习班,==>>猛戳这里

刘冬:CSDN博客专家、技术达人,拥有7年开发经验,先后做过Fortran/C/Java/PythonJS/Android等开发,创过业,做过技术总监,仍然对技术怀有情怀。 2014年开通CSDN博客,有81篇原创技术文章,访问量22万+。

下面是来自刘冬在CSDN Python学习班的分享:

小步慢跑,聊聊Python中的多线程

在开始今天的话题之前,简单的来看有关Python的体系结构。为了方便起见我做一张导图,让大家有个宏观的认识。

图片描述

今天本来准备全面的聊聊有关高性能并发这个话题来着,但是周末马上要来了啊。所以我就取了其中的一点来介绍,关于其他的方面,有兴趣的小伙伴可以和我交流。谈高效并发,往往脱离不了以下三种方案:

  • 进程:每个逻辑控制流都是一个进程,由内核来调度和维护。因为进程有独立的虚拟地址空间,想要和其他控制流通信必须依靠显示的进程间通信,即我们所说的IPC机制
  • 线程:线程应该是我们最为熟知的。它本质是运行在一个单一进程上下文中的逻辑流,由内核进行调度。
  • I/O多路复用:应用程序在一个进程的上下文中显式地调度他们自己的逻辑流.逻辑流被模型化为状态机,数据到达文件描述符之后,主程序显式地从一个状态转换为另一个状态。因为程序都是以一个单独的进程,所以所有的流都共享同一个地址空间。基本的思路就是使用select函数要求内核挂起进程,只有一个或多个I/O事件发生后,才将控制权返回给应用程序。

    看起来令人难以理解,但幸运的是Python中针对这三方面都提供了响应的支持,简化了我们的操作。那今天咱就聊聊其中的一点–线程。为什么选择线程呢?一方面考虑到大部分人都有线程这个概念,另一方面考虑相比进程线程更轻量级,相比协程,线程更易于理解。进程和线程之间的关系可以用衣服最简单的图来表示:

这里写图片描述

线程的状态

任何一门支持线程的语言都可以具备以下几种运行状态,无论是你做Java、Python还是C,首先来看下面一张图:

这里写图片描述

在这里我简单来解释以下这几种状态的含义:

  • 新建:使用线程的第一步就是创建线程,创建后的线程只是进入可执行的状态,也就是Runnable

  • Runnable:进入此状态的线程还并未开始运行,一旦CPU分配时间片给这个线程后,该线程才正式的开始运行

  • Running:线程正式开始运行,在运行过程中线程可能会进入阻塞的状态,即Blocked

  • Blocked:在该状态下,线程暂停运行,解除阻塞后,线程会进入Runnable状态,等待CPU再次分配时间片给它

  • 结束:线程方法执行完毕或者因为异常终止返回

其中最复杂的是线程从Running进入Blocked状态,通常有三种情况:

  • 睡眠:线程主动调用sleep()或join()方法后

  • 等待:线程中调用wait()方法,此时需要有其他线程通过notify()方法来唤醒

  • 同步:线程中获取线程锁,但是因为资源已经被其他线程占用时

到现在,我们对线程有个基本的概念,光说不练假把式,下面我们就通过是三个小的示例来聊聊线程的使用以及线程中最终的两个概念:同步和通信。

线程简单使用

Python当中要实现多线程有两种方式:一种是使用低级的_thread模块,另一种高级threading模块,相比而言,我推荐使用threading模块。另外我只推荐用于多线程用于处理有关I/O的操作,不然反而造成性能下降。在开始之前呢,先来了解下threading模块给我提供哪些常用的类: Thread,Lock,RLock,Condition,Event,Semaphore,Timer和Local。这几个类可谓开发多线程中的神兵利器。但是介于篇幅,咱就不展开讲了。

我们直接来看如何使用多线程,这才是至关重要的,有句老话是这么说的:要想让小孩子跑得先让他学会走。我们这就走两步:

import threading
def run(number): 
print(threading.currentThread().getName() + '\n') 
print(number)
if name == '__main': for i in range(10):#创建线程mythread = threading.Thread(target=run, args=(i,)) mythread.start()

多线程的创建和运行都是套路啊,写的多了自然熟了,来看看运行结果:

Thread-1,value=0 
Thread-2,value=1 
Thread-3,value=2 
Thread-4,value=3 
Thread-5,value=4 
Thread-6,value=5 
Thread-7,value=6 
Thread-8,value=7 
Thread-9,value=8 
Thread-10,value=9 

同步与通信

多线程开发中最难的问题不是如何使用,而是如何写出正确高效的代码,要写出正确而高效的代码必须要理解两个很重要的概念:同步和通信。所谓的通信指的是线程之间如何交换消息,而同步则用于控制不同线程之间操作发生的相对顺序。简单点说同步就是控制多个线程访问代码的顺序,通信就是线程之间如何传递消息。在python中实现同步的最简单的方案就是使用锁机制,实现通信最简单的方案就是Event。下面就来看看这两者的具体使用。

线程同步

当多个线程同时访问同一资源的时候,就会发生竞争,这有点像很多个男性都在追同一个妹纸一样,结果是不可预期的。因此有必要使用某种机制来保证每个男生都有机会和女生相处,这有点像将小姑娘放在一间房子里,然后进去的男生锁上门。下一个男生要想进去。必须等待上一个男生出来。只不过在这里叫线程锁。

Python的threading模块为我们提供了线程锁功能,在threading中提供RLock对象,RLock对象内部维护着一个Lock对象,它是一种可重入锁。对于Lock对象而言,如果一个线程连续两次进行acquire操作,那么由于第一次acquire之后没有release,第二次acquire将挂起线程。这会导致Lock对象永远不会release,使得线程死锁。而RLock对象允许一个线程多次对其进行acquire操作,因为在其内部通过一个counter变量维护着线程acquire的次数。而且每一次的acquire操作必须有一个release操作与之对应,在所有的release操作完成之后,别的线程才能申请该RLock对象。

通过锁机制,最终多线程访问共享资源的过程就类似以下:

图片描述

上图其实演示了在使用锁来解决线程同步最本质的一点:将所有线程对共享资源的读写操作串行化。

import threadingmylock = threading.RLock()
num = 0class WorkThread(threading.Thread):def __init__(self, name):threading.Thread.__init__(self)self.t_name = namedef run(self):global numwhile True:mylock.acquire()print('\n%s locked, number: %d' % (self.t_name, num))if num >= 4:mylock.release()print('\n%s released, number: %d' % (self.t_name, num))breaknum += 1print('\n%s released, number: %d' % (self.t_name, num))mylock.release()def test():thread1 = WorkThread('A-Worker')thread2 = WorkThread('B-Worker')thread1.start()thread2.start()if __name__ == '__main__':test() 

来看看运行结果:

A-Worker locked, number: 0A-Worker released, number: 1A-Worker locked, number: 1A-Worker released, number: 2A-Worker locked, number: 2A-Worker released, number: 3A-Worker locked, number: 3A-Worker released, number: 4A-Worker locked, number: 4A-Worker released, number: 4B-Worker locked, number: 4B-Worker released, number: 4

有些同学会问除了Lock和RLock还有其他的方式来实现类似的效果么?当然,比如Condition和Semaphore都有类似的功能,其中Condition是在Lock/RLock的基础上再次包装而成,而Semaphore的原理和操作系统的PV操作一致。之所以不细说的原因在于基本他们的基本使用和原理并无本质区别。我个人也一直认为越复杂的东西背后越是有简单的原理,当然欢迎有兴趣的同学和我进行探讨。

线程通信

在很多时候,我们需要在线程间传递消息,也叫作线程通信。Python中提供的Event就是最简单的通信机制之一。使用threading.Event可以使一个线程等待其他线程的通知,我们把这个Event传递到线程对象中,Event默认内置了一个标志,初始值为False。一旦该线程通过wait()方法进入等待状态,直到另一个线程调用该Event的set()方法将内置标志设置为True时,该Event会通知所有等待状态的线程恢复运行。先来看看Event中一些常用的方法:

方法名含义
isSet()测试内置的标识是否为True
set()将标识设置为True,并通知所有处于阻塞状态的线程恢复运行
clear()将标识设置为False
wait([timeout])如果标识为True时立即返回,否则阻塞线程至阻塞状态,等待其他线程调用set()

来看个简单示例,我们暂且假设你有6个妹纸需要叫她们起床,这时候你该怎么做呢?

import threading
import timeclass WorkThread(threading.Thread):def __init__(self, signal):threading.Thread.__init__(self)self.singal = signaldef run(self):print("妹纸 %s,睡觉了 ..." % self.name)self.singal.wait()print("妹纸 %s, 起床..." % self.name)if __name__ == "__main__":singal = threading.Event()for t in range(0, 6):thread = WorkThread(singal)thread.start()print("三秒钟后叫妹纸起床 ")time.sleep(3)#唤醒阻塞中的妹纸singal.set()

这里的的你就充当了主线程,每个妹纸就是一个子线程,不出意外三秒之后你就会按时唤醒所有的妹纸了:

妹纸 Thread-1,睡觉了 ...
妹纸 Thread-2,睡觉了 ...
妹纸 Thread-3,睡觉了 ...
妹纸 Thread-4,睡觉了 ...
妹纸 Thread-5,睡觉了 ...
妹纸 Thread-6,睡觉了 ...
三秒钟后叫妹纸起床 
妹纸 Thread-1, 起床...
妹纸 Thread-2, 起床...
妹纸 Thread-5, 起床...
妹纸 Thread-4, 起床...
妹纸 Thread-3, 起床...
妹纸 Thread-6, 起床...

使用Event实现线程通信通信固然可以,但是另一种进行线程通信的方式是借助队列,也就是Queue。在python的标准库中提供了线程安全的队列,基于FIFO(先进先出)实现,可以方便的帮助我们实现线程间的消息传递,使用非常简单,其原理也不难,用一张简单的图展示:

图片描述
另外,凡是符合该种结构的多线程通信过程我们称之为生产者-消费者模型。

线程池

其实,有关多线程的使用时非常简单的,更多的是根据具体的业务情况编写相应的逻辑.初次之外,考虑处理器的资源毕竟是有限的,不能一味的创建线程,我曾看到有些小伙伴在写爬虫的时候,100个url就创建了100个线程,其后果可想而知,因此当有需求要用到很多线程时,考虑使用线程池技术。
分享结束后,同学们还向刘冬请教了在工作学习中遇到的相关问题,比如

  • 生产者消费者模式是不是类似于发布者订阅者模式?
  • 请问已经存在全局锁了,为什么还会有threading.Lock等? 不明白全局锁的作用是什么
  • 单线程的IO复用(REACTOR)和多线程版的IO复用区别在哪
  • 关于Thread类,target参数,传入的函数如果有返回值如何处理

如果你是Python爱好者、或者正准备学习Python开发知识,CSDN Python学习班都会敞开双臂欢迎你,别犹豫,赶快加入我们!

详情,请点击:http://geek.csdn.net/news/detail/159636

这篇关于python 多线程,CSDN Python 学习班,整理自微信文字课程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/316290

相关文章

Python将博客内容html导出为Markdown格式

《Python将博客内容html导出为Markdown格式》Python将博客内容html导出为Markdown格式,通过博客url地址抓取文章,分析并提取出文章标题和内容,将内容构建成html,再转... 目录一、为什么要搞?二、准备如何搞?三、说搞咱就搞!抓取文章提取内容构建html转存markdown

Python获取中国节假日数据记录入JSON文件

《Python获取中国节假日数据记录入JSON文件》项目系统内置的日历应用为了提升用户体验,特别设置了在调休日期显示“休”的UI图标功能,那么问题是这些调休数据从哪里来呢?我尝试一种更为智能的方法:P... 目录节假日数据获取存入jsON文件节假日数据读取封装完整代码项目系统内置的日历应用为了提升用户体验,

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Python Websockets库的使用指南

《PythonWebsockets库的使用指南》pythonwebsockets库是一个用于创建WebSocket服务器和客户端的Python库,它提供了一种简单的方式来实现实时通信,支持异步和同步... 目录一、WebSocket 简介二、python 的 websockets 库安装三、完整代码示例1.

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专