我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Python进程和线程知识点举例分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Python进程和线程知识点举例分析

本篇内容主要讲解“Python进程和线程知识点举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python进程和线程知识点举例分析”吧!

多线程

一个进程至少包含一个线程,其实进程就是由若干个线程组成的。线程是操作系统直接支持的执行单元,因此高级语言通常都内置多线程的支持,Python 也不例外,而且Python 的线程是真正的 Posix Thread ,而不是模拟出来的线程。

多线程的运行有如下优点:

  • 使用线程可以把占据长时间的程序中的任务放到后台去处理。

  • 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。

  • 程序的运行速度可能加快。

  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

线程可以分为:

  • 内核线程:由操作系统内核创建和撤销。

  • 用户线程:不需要内核支持而在用户程序中实现的线程。

Python 的标准库提供了两个模块:_thread 和 threading,前者是低级模块,后者是高级模块,对 _thread 进行了封装。大多数情况只需要采用 threading模块即可,并且也推荐采用这个模块。

这里再次以下载文件作为例子,用多线程的方式来实现一遍:

from random import randintfrom threading import Thread, current_threadfrom time import time, sleepdef download(filename): print('thread %s is running...' % current_thread().name) print('开始下载%s...' % filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))def download_multi_threading(): print('thread %s is running...' % current_thread().name) start = time() t1 = Thread(target=download, args=('Python.pdf',), name='subthread-1') t1.start() t2 = Thread(target=download, args=('nazha.mkv',), name='subthread-2') t2.start() t1.join() t2.join() end = time() print('总共耗费了%.3f秒' % (end - start)) print('thread %s is running...' % current_thread().name)if __name__ == '__main__': download_multi_threading()

实现多线程的方式和多进程类似,也是通过 Thread 类创建线程对象,target 参数表示传入需要执行的函数,args 参数是表示传给函数的参数,然后 name 是给当前线程进行命名,默认命名是如 Thread- 1、Thread-2 等等。

此外,任何进程默认会启动一个线程,我们将它称为主线程,主线程又可以启动新的线程,在 threading 模块中有一个函数 current_thread() ,可以返回当前线程的实例。主线程实例的名字叫 MainThread,子线程的名字是在创建的时候指定,也就是 name 参数。

运行结果:

thread MainThread is running...thread subthread-1 is running...开始下载Python.pdf...thread subthread-2 is running...开始下载nazha.mkv...nazha.mkv下载完成! 耗费了5秒Python.pdf下载完成! 耗费了7秒总共耗费了7.001秒thread MainThread is running...

Lock

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

下面是一个例子,演示了多线程同时操作一个变量,如何把内存给改乱了:

from threading import Threadfrom time import time, sleep# 假定这是你的银行存款:balance = 0def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - ndef run_thread(n): for i in range(100000): change_it(n)def nolock_multi_thread(): t1 = Thread(target=run_thread, args=(5,)) t2 = Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print(balance)if __name__ == '__main__': nolock_multi_thread()

运行结果:

-8

代码中定义了一个共享变量 balance,然后启动两个线程,先存后取,理论上结果应该是 0 。但是,由于线程的调度是由操作系统决定的,当 t1、t2 交替执行时,只要循环次数足够多,balance 的结果就不一定是0了。

原因就是下面这条语句:

balance = balance + n

这条语句的执行分为两步的:

  • 先计算 balance + n,保存结果到一个临时变量

  • 将临时变量的值赋给 balance

也就是可以看成:

x = balance+nbalance=x

正常运行如下所示:

初始值 balance = 0t1: x1 = balance + 5 # x1 = 0 + 5 = 5t1: balance = x1 # balance = 5t1: x1 = balance - 5 # x1 = 5 - 5 = 0t1: balance = x1 # balance = 0t2: x2 = balance + 8 # x2 = 0 + 8 = 8t2: balance = x2 # balance = 8t2: x2 = balance - 8 # x2 = 8 - 8 = 0t2: balance = x2 # balance = 0结果 balance = 0

但实际上两个线程是交替运行的,也就是:

初始值 balance = 0t1: x1 = balance + 5 # x1 = 0 + 5 = 5t2: x2 = balance + 8 # x2 = 0 + 8 = 8t2: balance = x2 # balance = 8t1: balance = x1 # balance = 5t1: x1 = balance - 5 # x1 = 5 - 5 = 0t1: balance = x1 # balance = 0t2: x2 = balance - 8 # x2 = 0 - 8 = -8t2: balance = x2 # balance = -8结果 balance = -8

简单说,就是因为对 balance 的修改需要多条语句,而执行这几条语句的时候,线程可能中断,导致多个线程把同个对象的内容该乱了。

要保证计算正确,需要给 change_it() 添加一个锁,添加锁后,其他线程就必须等待当前线程执行完并释放锁,才可以执行该函数。并且锁是只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁。通过 threading 模块的 Lock 实现。

因此代码修改为:

from threading import Thread, Lockfrom time import time, sleep# 假定这是你的银行存款:balance = 0lock = Lock()def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - ndef run_thread_lock(n): for i in range(100000): # 先要获取锁: lock.acquire() try: # 放心地改吧: change_it(n) finally: # 改完了一定要释放锁: lock.release()def nolock_multi_thread(): t1 = Thread(target=run_thread_lock, args=(5,)) t2 = Thread(target=run_thread_lock, args=(8,)) t1.start() t2.start() t1.join() t2.join() print(balance)if __name__ == '__main__': nolock_multi_thread()

但遗憾的是 Python 并不能完全发挥多线程的作用,这里可以通过写一个死循环,然后通过任务管理器查看进程的 CPU 使用率。

正常来说,如果有两个死循环线程,在多核CPU中,可以监控到会占用200%的CPU,也就是占用两个CPU核心。

要想把 N 核CPU的核心全部跑满,就必须启动 N 个死循环线程。

死循环代码如下所示:

import threading, multiprocessingdef loop(): x = 0 while True: x = x ^ 1for i in range(multiprocessing.cpu_count()): t = threading.Thread(target=loop) t.start()

在 4 核CPU上可以监控到 CPU 占用率仅有102%,也就是仅使用了一核。

但是用其他编程语言,比如C、C++或 Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?

因为 Python 的线程虽然是真正的线程,但解释器执行代码时,有一个 GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个 GIL 全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

GIL是 Python 解释器设计的历史遗留问题,通常我们用的解释器是官方实现的 CPython,要真正利用多核,除非重写一个不带GIL的解释器。

尽管多线程不能完全利用多核,但对于程序的运行效率提升还是很大的,如果想实现多核任务,可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

ThreadLocal

采用多线程的时候,一个线程采用自己的局部变量会比全局变量更好,原因前面也介绍了,如果不加锁,多个线程可能会乱改某个全局变量的数值,而局部变量是只有每个线程自己可见,不会影响其他线程。

不过,局部变量的使用也有问题,就是函数调用时候,传递起来会比较麻烦,即如下所示:

def process_student(name): std = Student(name) # std是局部变量,但是每个函数都要用它,因此必须传进去: do_task_1(std) do_task_2(std)def do_task_1(std): do_subtask_1(std) do_subtask_2(std)def do_task_2(std): do_subtask_2(std) do_subtask_2(std)

局部变量需要一层层传递给每个函数,比较麻烦,有没有更好的办法呢?

一个思路是用一个全局的 dict ,然后用每个线程作为 key ,代码例子如下所示:

global_dict = {}def std_thread(name): std = Student(name) # 把std放到全局变量global_dict中: global_dict[threading.current_thread()] = std do_task_1() do_task_2()def do_task_1(): # 不传入std,而是根据当前线程查找: std = global_dict[threading.current_thread()] ...def do_task_2(): # 任何函数都可以查找出当前线程的std变量: std = global_dict[threading.current_thread()]

这种方式理论上是可行的,它可以避免局部变量在每层函数中传递,只是获取局部变量的代码不够优雅,在 threading 模块中提供了 local 函数,可以自动完成这件事情,代码如下所示:

import threading# 创建全局ThreadLocal对象:local_school = threading.local()def process_student(): # 获取当前线程关联的student: std = local_school.student print('Hello, %s (in %s)' % (std, threading.current_thread().name))def process_thread(name): # 绑定ThreadLocal的student: local_school.student = name process_student()t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')t1.start()t2.start()t1.join()t2.join()

运行结果:

Hello, Alice (in Thread-A)Hello, Bob (in Thread-B)

在代码中定义了一个全局变量 local_school ,它是一个 ThreadLocal 对象,每个线程都可以对它读写 student 属性,但又不会互相影响,也不需要管理锁的问题,这是 ThreadLocal 内部会处理。

ThreadLocal 最常用的是为每个线程绑定一个数据库连接,HTTP 请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

进程 vs 线程

我们已经分别介绍了多进程和多线程的实现方式,那么究竟应该选择哪种方法来实现并发编程呢,这两者有什么优缺点呢?

通常多任务的实现,我们都是设计 Master-Worker,Master 负责分配任务,Worker 负责执行任务,因此多任务环境下,通常是一个 Master 和多个 Worker。

如果用多进程实现 Master-Worker,主进程就是 Master,其他进程就是 Worker。

如果用多线程实现 Master-Worker,主线程就是 Master,其他线程就是 Worker。

对于多进程,最大的优点就是稳定性高,因为一个子进程挂了,不会影响主进程和其他子进程。当然主进程挂了,所有进程自然也就挂,但主进程只是负责分配任务,挂掉概率非常低。著名的 Apache 最早就是采用多进程模式。

缺点有:

  • 创建进程代价大,特别是在 windows 系统,开销巨大,而 Unix/ Linux 系统因为可以调用 fork() ,所以开销还行;

  • 操作系统可以同时运行的进程数量有限,会受到内存和 CPU 的限制。

对于多线程,通常会快过多进程,但也不会快太多;缺点就是稳定性不好,因为所有线程共享进程的内存,一个线程挂断都可能直接造成整个进程崩溃。比如在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程。

进程/线程切换

是否采用多任务模式,第一点需要注意的就是,一旦任务数量过多,效率肯定上不去,这主要是切换进程或者线程是有代价的。

操作系统在切换进程或者线程时的流程是这样的:

  • 先保存当前执行的现场环境(CPU寄存器状态、内存页等)

  • 然后把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等)

  • 开始执行任务

这个切换过程虽然很快,但是也需要耗费时间,如果任务数量有上千个,操作系统可能就忙着切换任务,而没有时间执行任务,这种情况最常见的就是硬盘狂响,点窗口无反应,系统处于假死状态。

计算密集型vsI/O密集型

采用多任务的第二个考虑就是任务的类型,可以将任务分为计算密集型和 I/O 密集型。

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如对视频进行编码解码或者格式转换等等,这种任务全靠 CPU 的运算能力,虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU 执行任务的效率就越低。计算密集型任务由于主要消耗CPU资源,这类任务用 Python这样的脚本语言去执行效率通常很低,最能胜任这类任务的是C语言,我们之前提到了 Python 中有嵌入 C/C++ 代码的机制。不过,如果必须用 Python 来处理,那最佳的就是采用多进程,而且任务数量最好是等同于 CPU 的核心数。

除了计算密集型任务,其他的涉及到网络、存储介质 I/O 的任务都可以视为 I/O 密集型任务,这类任务的特点是 CPU 消耗很少,任务的大部分时间都在等待 I/O 操作完成(因为 I/O 的速度远远低于 CPU 和内存的速度)。对于 I/O 密集型任务,如果启动多任务,就可以减少 I/O 等待时间从而让 CPU 高效率的运转。一般会采用多线程来处理 I/O 密集型任务。

异步 I/O

现代操作系统对 I/O 操作的改进中最为重要的就是支持异步 I/O。如果充分利用操作系统提供的异步 I/O 支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型。Nginx 就是支持异步 I/O的 Web 服务器,它在单核 CPU 上采用单进程模型就可以高效地支持多任务。在多核 CPU 上,可以运行多个进程(数量与CPU核心数相同),充分利用多核 CPU。用 Node.js 开发的服务器端程序也使用了这种工作模式,这也是当下实现多任务编程的一种趋势。

在 Python 中,单线程+异步 I/O 的编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。协程最大的优势就是极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。协程的第二个优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不用加锁,只需要判断状态就好了,所以执行效率比多线程高很多。如果想要充分利用CPU的多核特性,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

到此,相信大家对“Python进程和线程知识点举例分析”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Python进程和线程知识点举例分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python进程和线程知识点举例分析

本篇内容主要讲解“Python进程和线程知识点举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python进程和线程知识点举例分析”吧!多线程一个进程至少包含一个线程,其实进程就是由若干个
2023-06-02

如何进行Ruby线程相关知识点分析

这期内容当中小编将会给大家带来有关如何进行Ruby线程相关知识点分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Ruby语言一款完全面向对象的解释型脚本语言。对于这样的一款新型编程语言,其特性对于程序员
2023-06-17

Python的进程,线程和协程实例分析

这篇“Python的进程,线程和协程实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python的进程,线程和协程实例
2023-06-29

Python线程操作问题举例分析

这篇文章主要介绍“Python线程操作问题举例分析”,在日常操作中,相信很多人在Python线程操作问题举例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python线程操作问题举例分析”的疑惑有所帮助!
2023-06-17

python多进程和VNPY多进程参数优化举例分析

这篇文章主要讲解了“python多进程和VNPY多进程参数优化举例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“python多进程和VNPY多进程参数优化举例分析”吧!首先,由于GIL(
2023-06-02

C#多线程举例分析

这篇文章主要讲解了“C#多线程举例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#多线程举例分析”吧!线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中
2023-06-22

Python多线程机制接口举例分析

本篇内容介绍了“Python多线程机制接口举例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Python开发工具是一个具有更高层的多线程
2023-06-17

Linux中进程和线程的示例分析

这篇文章主要为大家展示了“Linux中进程和线程的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Linux中进程和线程的示例分析”这篇文章吧。计算机实际上可以做的事情实质上非常简单,比如
2023-06-13

Node.js中进程和线程的示例分析

这篇文章给大家分享的是有关Node.js中进程和线程的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。进程与线程是一个程序员的必知概念,面试经常被问及,但是一些文章内容只是讲讲理论知识,可能一些小伙伴并没有
2023-06-15

java中进程和线程的示例分析

小编给大家分享一下java中进程和线程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为什么会有进程在简单的批处理操作系统中,作业时串行执行的,即一个作业
2023-06-20

Python基础知识点的示例分析

这篇文章给大家分享的是有关Python基础知识点的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、python中的标志符:1、给变量取的名字就是标志符2、区分大小写,MyName和myname是两个不同
2023-06-29

python爬虫中多线程和多进程的示例分析

小编给大家分享一下python爬虫中多线程和多进程的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象的脚本语言,其最初的设计是用于
2023-06-14

C# .NET多线程应用举例分析

这篇文章主要介绍“C# .NET多线程应用举例分析”,在日常操作中,相信很多人在C# .NET多线程应用举例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C# .NET多线程应用举例分析”的疑惑有所帮助!
2023-06-17

Python多进程知识点有哪些

这篇文章主要讲解了“Python多进程知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python多进程知识点有哪些”吧!一、什么是多进程?1. 进程程序:例如xxx.py这是程序
2023-06-30

Python入门基础知识点实例分析

这篇文章主要介绍“Python入门基础知识点实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python入门基础知识点实例分析”文章能帮助大家解决问题。标识符在 Python 中,所有标识符可
2023-07-04

Python编码规范知识点实例分析

这篇文章主要讲解了“Python编码规范知识点实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python编码规范知识点实例分析”吧!1 代码编码格式一般来说,声明编码格式在脚本中是必
2023-07-02

python中pandas的知识点的示例分析

这篇文章主要介绍python中pandas的知识点的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!python的数据类型有哪些?python的数据类型:1. 数字类型,包括int(整型)、long(长整型)和
2023-06-14

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录