返回顶部
首页 > 资讯 > 后端开发 > Python >Python进程和线程知识点举例分析
  • 954
分享到

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

2023-06-02 02:06:44 954人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

本篇内容主要讲解“python进程和线程知识点举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“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进程和线程知识点举例分析”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

--结束END--

本文标题: Python进程和线程知识点举例分析

本文链接: https://lsjlt.com/news/228530.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python进程和线程知识点举例分析
    本篇内容主要讲解“Python进程和线程知识点举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python进程和线程知识点举例分析”吧!多线程一个进程至少包含一个线程,其实进程就是由若干个...
    99+
    2023-06-02
  • 如何进行Ruby线程相关知识点分析
    这期内容当中小编将会给大家带来有关如何进行Ruby线程相关知识点分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Ruby语言一款完全面向对象的解释型脚本语言。对于这样的一款新型编程语言,其特性对于程序员...
    99+
    2023-06-17
  • Python的进程,线程和协程实例分析
    这篇“Python的进程,线程和协程实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python的进程,线程和协程实例...
    99+
    2023-06-29
  • C#多线程举例分析
    这篇文章主要讲解了“C#多线程举例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#多线程举例分析”吧!线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中...
    99+
    2023-06-22
  • python多进程和VNPY多进程参数优化举例分析
    这篇文章主要讲解了“python多进程和VNPY多进程参数优化举例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“python多进程和VNPY多进程参数优化举例分析”吧!首先,由于GIL(...
    99+
    2023-06-02
  • Python线程操作问题举例分析
    这篇文章主要介绍“Python线程操作问题举例分析”,在日常操作中,相信很多人在Python线程操作问题举例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python线程操作问题举例分析”的疑惑有所帮助!...
    99+
    2023-06-17
  • Python多线程机制接口举例分析
    本篇内容介绍了“Python多线程机制接口举例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Python开发工具是一个具有更高层的多线程...
    99+
    2023-06-17
  • vuex进阶知识点的示例分析
    这篇文章将为大家详细讲解有关vuex进阶知识点的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、 Gettercomputed:{   ge...
    99+
    2024-04-02
  • MySQL主线程状态举例分析
    这篇文章主要介绍“MySQL主线程状态举例分析”,在日常操作中,相信很多人在MySQL主线程状态举例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”MySQL主线程状态举例...
    99+
    2024-04-02
  • Python基础知识点的示例分析
    这篇文章给大家分享的是有关Python基础知识点的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、python中的标志符:给变量取的名字就是标志符区分大小写,MyName和myname是两个不同的标志符...
    99+
    2023-06-29
  • Python 多线程知识点总结及实例用法
    Python 多线程 多线程类似于同时执行多个不同程序,多线程运行有如下优点: 使用线程可以把占据长时间的程序中的任务放到后台去处理。 用户界面可以更加吸引人,这...
    99+
    2024-04-02
  • Node.js中进程和线程的示例分析
    这篇文章给大家分享的是有关Node.js中进程和线程的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。进程与线程是一个程序员的必知概念,面试经常被问及,但是一些文章内容只是讲讲理论知识,可能一些小伙伴并没有...
    99+
    2023-06-15
  • Linux中进程和线程的示例分析
    这篇文章主要为大家展示了“Linux中进程和线程的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Linux中进程和线程的示例分析”这篇文章吧。计算机实际上可以做的事情实质上非常简单,比如...
    99+
    2023-06-13
  • java中进程和线程的示例分析
    小编给大家分享一下java中进程和线程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为什么会有进程在简单的批处理操作系统中,作业时串行执行的,即一个作业...
    99+
    2023-06-20
  • Python多进程知识点有哪些
    这篇文章主要讲解了“Python多进程知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python多进程知识点有哪些”吧!一、什么是多进程?1. 进程程序:例如xxx.py这是程序...
    99+
    2023-06-30
  • C# .NET多线程应用举例分析
    这篇文章主要介绍“C# .NET多线程应用举例分析”,在日常操作中,相信很多人在C# .NET多线程应用举例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C# .NET多线程应用举例分析”的疑惑有所帮助!...
    99+
    2023-06-17
  • python爬虫中多线程和多进程的示例分析
    小编给大家分享一下python爬虫中多线程和多进程的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象的脚本语言,其最初的设计是用于...
    99+
    2023-06-14
  • Python基础知识点分析
    本篇内容介绍了“Python基础知识点分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Python简介Python的历史1989年圣诞节:...
    99+
    2023-06-02
  • python中pandas的知识点的示例分析
    这篇文章主要介绍python中pandas的知识点的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!python的数据类型有哪些python的数据类型:1. 数字类型,包括int(整型)、long(长整型)和f...
    99+
    2023-06-14
  • Python编码规范知识点实例分析
    这篇文章主要讲解了“Python编码规范知识点实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python编码规范知识点实例分析”吧!1 代码编码格式一般来说,声明编码格式在脚本中是必...
    99+
    2023-07-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作