返回顶部
首页 > 资讯 > 后端开发 > Python >深度解析Python线程和进程
  • 182
分享到

深度解析Python线程和进程

2024-04-02 19:04:59 182人浏览 八月长安

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

摘要

目录什么是进程什么是线程线程与进程的区别并行与并发python中的多进程Python中进程操作线程Python的threading模块锁Lock:全局解释器锁(GIL)参考文章:什么

什么是进程

进程就是操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据,操作系统管理所有进程的执行,为它们合理的分配资源。

每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全

什么是线程

一个进程还可以拥有多个并发的执行线索,简单的说就是拥有多个可以获得CPU调度的执行单元,这就是所谓的线程。

CPU调度和分派的基本单位线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)。

由于线程在同一个进程下,它们可以共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

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

线程缺点:

多线程也并不是没有坏处,站在其他进程的角度,多线程的程序对其他程序并不友好,因为它占用了更多的CPU执行时间,导致其他程序无法获得足够的CPU执行时间;另一方面,站在开发者的角度,编写和调试多线程的程序都对开发者有较高的要求,对于初学者来说更加困难。

线程与进程的区别

  • 地址空间和其他资源:进程间相互独立,同一进程的各线程间共享。某线程内的想爱你城咋其他进程不可见。
  • 通信:进程间通信IPC,线程间可以直接读写进程数据段来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  • 调度和切换:线程上下文切换比进程上下文切换要快得多。
  • 在多线程操作系统中,进程不是一个可执行的实体

并行与并发

并行(Parallelism)

并行:指两个或两个以上事件(或线程)在同一时刻发生,是真正意义上的不同事件或线程在同一时刻,在不同CPU资源呢上(多核),同时执行。

特点

  • 同一时刻发生,同时执行。
  • 不存在像并发那样竞争,等待的概念。

并发(Concurrency)

指一个物理CPU(也可以多个物理CPU) 在若干道程序(或线程)之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。

特点

  • 微观角度:所有的并发处理都有排队等候,唤醒,执行等这样的步骤,在微观上他们都是序列被处理的,如果是同一时刻到达的请求(或线程)也会根据优先级的不同,而先后进入队列排队等候执行。
  • 宏观角度:多个几乎同时到达的请求(或线程)在宏观上看就像是同时在被处理。

Python中的多进程

Python中进程操作

process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

法:Process([group [, target [, name [, args [, kwargs]]]]])

由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)。

注意:

  1. 必须使用关键字方式来指定参数;
  2. args指定的为传给target函数的位置参数,是一个元祖形式,必须有逗号。

参数介绍:

  • group:参数未使用,默认值为None。
  • target:表示调用对象,即子进程要执行的任务。
  • args:表示调用的位置参数元祖。
  • kwargs:表示调用对象的字典。如kwargs = {'name':Jack, 'age':18}。
  • name:子进程名称。

代码展示:

import os
from multiprocessing import Process
def func_one():
    print("第一个子进程")
    print("子进程(一)大儿子:%s  父进程:%s" % (os.getpid(), os.getppid()))
def func_two():
    print("第二个子进程")
    print("子进程(二)二儿子:%s  父进程:%s" % (os.getpid(), os.getppid()))
if __name__ == '__main__':
    p_one = Process(target=func_one)
    P_two = Process(target=func_two)
    p_one.start()
    P_two.start()
    print("子进程:%s  父进程:%s" % (os.getpid(), os.getppid()))  

继承Process的方式开启进程的方式:

import os
from multiprocessing import Process
def func_one():
    print("第一个子进程")
    print("子进程(一)大儿子:%s  父进程:%s" % (os.getpid(), os.getppid()))
def func_two():
    print("第二个子进程")
    print("子进程(二)二儿子:%s  父进程:%s" % (os.getpid(), os.getppid()))
if __name__ == '__main__':
    p_one = Process(target=func_one)
    P_two = Process(target=func_two)
    p_one.start()
    P_two.start()
    print("子进程:%s  父进程:%s" % (os.getpid(), os.getppid()))  

线程

Python的threading模块

在Python早期的版本中就引入了thread模块(现在名为_thread)来实现多线程编程,然而该模块过于底层,而且很多功能都没有提供,因此目前的多线程开发我们推荐使用threading模块,该模块对多线程编程提供了更好的面向对象的封装。

from random import randint
from threading import Thread
from time import time, sleep
def download(filename):
    print('开始下载%s...' % filename)
    time_to_download = randint(5, 10)
    sleep(time_to_download)
    print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
def main():
    start = time()
    t1 = Thread(target=download, args=('Python从入门到住院.pdf',))
    t1.start()
    t2 = Thread(target=download, args=('Peking Hot.avi',))
    t2.start()
    #join阻塞完成任务
    t1.join()
    t2.join()
    end = time()
    print('总共耗费了%.3f秒' % (end - start))
if __name__ == '__main__':
    main()

我们可以直接使用threading模块的Thread类来创建线程,但是我们之前讲过一个非常重要的概念叫“继承”,我们可以从已有的类创建新类,因此也可以通过继承Thread类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。

代码如下所示:

from random import randint
from threading import Thread
from time import time, sleep
class DownloadTask(Thread):
    def __init__(self, filename):
        super().__init__()
        self._filename = filename
    def run(self):
        print('开始下载%s...' % self._filename)
        time_to_download = randint(5, 10)
        sleep(time_to_download)
        print('%s下载完成! 耗费了%d秒' % (self._filename, time_to_download))
def main():
    start = time()
    t1 = DownloadTask('Python从入门到住院.pdf')
    t1.start()
    t2 = DownloadTask('Peking Hot.avi')
    t2.start()
    t1.join()
    t2.join()
    end = time()
    print('总共耗费了%.2f秒.' % (end - start))
if __name__ == '__main__':
    main()

锁Lock:

模拟场景:

演示了100个线程向同一个银行账户转账(转入1元钱)的场景,在这个例子中,银行账户就是一个临界资源,在没有保护的情况下我们很有可能会得到错误的结果。

from time import sleep
from threading import Thread
class Account(object):
    #初始化账户余额为0元
    def __init__(self):
        self._balance = 0
    # 存款函数
    def deposit(self, money):
        # 计算存款后的余额
        new_balance = self._balance + money
        # 模拟受理存款业务需要0.01秒的时间
        sleep(0.01)
        # 修改账户余额
        self._balance = new_balance
    # set和get
    @property
    def balance(self):
        return self._balance
class AddMoneyThread(Thread):
    def __init__(self, account, money):
        super().__init__()
        self._account = account
        self._money = money
    def run(self):
        self._account.deposit(self._money)
def main():
    # 创建对象
    account = Account()
    threads = []
    # 创建100个存款的线程向同一个账户中存钱
    for _ in range(100):
        t = AddMoneyThread(account, 1)
        threads.append(t)
        t.start()
    # 等所有存款的线程都执行完毕
    for t in threads:
        t.join()
    print('账户余额为: ¥%d元' % account.balance)
if __name__ == '__main__':
    main()

运行结果:

100个线程分别向账户中转入1元钱,结果居然远远小于100元。

之所以出现这种情况是因为我们没有对银行账户这个“临界资源”加以保护,多个线程同时向账户中存钱时,会一起执行到new_balance = self._balance + money这行代码,多个线程得到的账户余额都是初始状态下的0,所以都是0上面做了+1的操作,因此得到了错误的结果。

在这种情况下,“锁”就可以派上用场了。我们可以通过“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。

加锁:

from time import sleep
from threading import Thread, Lock
class Account(object):
    def __init__(self):
        self._balance = 0
        self._lock = Lock()
    def deposit(self, money):
        # 先获取锁才能执行后续的代码
        self._lock.acquire()
        try:
            new_balance = self._balance + money
            sleep(0.01)
            self._balance = new_balance
        finally:
            # 在finally中执行释放锁的操作保证正常异常锁都能释放
            self._lock.release()
    @property
    def balance(self):
        return self._balance
class AddMoneyThread(Thread):
    def __init__(self, account, money):
        super().__init__()
        self._account = account
        self._money = money
    def run(self):
        # 运行存钱业务,只有获取锁的才能执行
        self._account.deposit(self._money)
def main():
    account = Account()
    threads = []
    #创建100个线程
    for _ in range(100):
        # 线程加钱
        t = AddMoneyThread(account, 1)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print('账户余额为: ¥%d元' % account.balance)
if __name__ == '__main__':
    main()

结果:账户余额为: ¥100元

比较遗憾的一件事情是Python的多线程并不能发挥CPU的多核特性,因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。

全局解释器锁(GIL)

GIL是一个互斥锁,它防止多个线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的 尽管Python完全支持多线程编程, 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的。

因此,解释器实际上被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。在多线程环境中,Python 虚拟机按以下方式执行:

  • 设置GIL
  • 切换到一个线程去执行
  • 运行

由于GIL的存在,Python的多线程不能称之为严格的多线程。因为多线程下每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行。

由于GIL的存在,即使是多线程,事实上同一时刻只能保证一个线程在运行,既然这样多线程的运行效率不就和单线程一样了吗,那为什么还要使用多线程呢?

由于以前的电脑基本都是单核CPU,多线程和单线程几乎看不出差别,可是由于计算机的迅速发展,现在的电脑几乎都是多核CPU了,最少也是两个核心数的,这时差别就出来了:通过之前的案例我们已经知道,即使在多核CPU中,多线程同一时刻也只有一个线程在运行,这样不仅不能利用多核CPU的优势,反而由于每个线程在多个CPU上是交替执行的,导致在不同CPU上切换时造成资源的浪费,反而会更慢。即原因是一个进程只存在一把gil锁,当在执行多个线程时,内部会争抢gil锁,这会造成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源。

但是在使用多线程抓取网页内容时,遇到io阻塞时,正在执行的线程会暂时释放GIL锁,这时其它线程会利用这个空隙时间,执行自己的代码,因此多线程抓取比单线程抓取性能要好,所以我们还是要使用多线程的。

参考文章:

深度解析Python线程和进程 - wyh草样 - 博客园

Python-100-Days/13.进程和线程.md at master · jackfrued/Python-100-Days · GitHub

到此这篇关于Python深度解析线程和进程的文章就介绍到这了,更多相关python线程和进程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 深度解析Python线程和进程

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

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

猜你喜欢
  • 深度解析Python线程和进程
    目录什么是进程什么是线程线程与进程的区别并行与并发Python中的多进程Python中进程操作线程Python的threading模块锁Lock:全局解释器锁(GIL)参考文章:什么...
    99+
    2024-04-02
  • 深入了解Python 中线程和进程区别
    目录一、什么是进程/线程1、引论2、线程3、进程4、区别5、使用二、多线程使用1、常用方法2、常用参数3、多线程的应用3.1重写线程法3.2直接调用法4、线程间数据的共享三、多进程使...
    99+
    2024-04-02
  • 深入浅析Node中的进程和线程
    // app.js const Koa = require('koa') const router = require('koa-router')() const app = new Koa() // 用来...
    99+
    2023-05-14
    nodejs​ 进程 线程
  • Java线程池源码的深度解析
    目录概述核心机制线程池工作原理线程池状态源码解析关键成员变量线程提交原理Woker运行原理总结概述 线程池的好处和使用本篇文章就不赘叙了,不了解的可以参考下面两篇文章: 一文全貌了解...
    99+
    2022-11-13
    Java线程池源码解析 Java线程池源码 Java线程池
  • 深入浅析python中的多进程、多线程、协程
    进程与线程的历史 我们都知道计算机是由硬件和软件组成的。硬件中的CPU是计算机的核心,它承担计算机的所有任务。 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配、任务的调度。 程序...
    99+
    2022-06-04
    多线程 进程 python
  • python线程、进程和协程详解
    引言 解释器环境:python3.5.1 我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程、多进程的模块。...
    99+
    2022-06-04
    线程 详解 进程
  • Python的进程,线程和协程实例分析
    这篇“Python的进程,线程和协程实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python的进程,线程和协程实例...
    99+
    2023-06-29
  • 详解Python中的进程和线程
    进程是什么? 进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执...
    99+
    2024-04-02
  • 深入解析Python中的多进程
    目录前言1.创建进程2.多进程中的Queue3.多进程与多线程的性能比较4.进程池pool5.共享内存6.进程锁lock前言 现在我们的计算机都是多个核的,通俗来说就是多个处理或者计...
    99+
    2024-04-02
  • Python高级教程之线程进程和协程的代码解析
    目录进程进程 5 种基本状态进程的特点进程间数据共享进程池进程的缺点线程线程的定义使用线程模块的简单示例代码解析协程协程与线程Python 协程协程的执行关闭协程链接协程以创建管道总...
    99+
    2024-04-02
  • 深度解析PHP8的新特性和优化程度
    PHP8带来了哪些提升?详解新特性与优化,需要具体代码示例 随着时间的推移,PHP成为了最受欢迎的Web开发语言之一。PHP8作为PHP的最新版本,在性能、安全性和语言特性上都带来了一系列重要的改进。本文将详细介绍PHP8带来的...
    99+
    2024-01-13
    优化 PHP 新特性
  • Python进程和线程知识点举例分析
    本篇内容主要讲解“Python进程和线程知识点举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python进程和线程知识点举例分析”吧!多线程一个进程至少包含一个线程,其实进程就是由若干个...
    99+
    2023-06-02
  • python教程之进程和线程
    目录进程和线程的区别和联系多进程线程池多线程总结进程和线程的区别和联系 终于开始加深难度,来到进程和线程的知识点~ 单就这两个概念,就难倒过不少初学者——今天...
    99+
    2024-04-02
  • Python的进程,线程和协程实例详解
    目录相关介绍实验环境进程多进程用进程池对多进程进行操作线程使用_thread模块实现使用 threading 模块实现协程使用asyncio模块实现总结相关介绍 Python是一种跨...
    99+
    2024-04-02
  • 实例详解Python的进程,线程和协程
    目录前言前提条件相关介绍实验环境进程多进程用进程池对多进程进行操作线程使用_thread模块实现使用 threading 模块实现协程使用asyncio模块实现总结前言 本文用Pyt...
    99+
    2024-04-02
  • Python中进程和线程的区别详解
    Num01>线程 线程是操作系统中能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。 一个线程指的是进程中一个单一顺序的控制流。 一个进程中可以并发多条线程,每条线程并行执行不同...
    99+
    2022-06-04
    线程 详解 进程
  • python 多进程和多线程使用详解
    目录进程和线程 Python的多进程 进程池 多进程间的数据通信与共享 Python的多线程 多线程间的数据共享 使用queue队列通信-经典的生产者和消费者模型进程和线程 进程是...
    99+
    2024-04-02
  • 深度源码解析Java 线程池的实现原理
    目录线程池的优点线程池的实现原理ThreadPoolExecutor阻塞队列线程池工厂拒绝策略提交任务到线程池execute 方法submit 方法关闭线程池合理的参数7、本文小结j...
    99+
    2024-04-02
  • Android 进程和线程详解
      当启动一个应用程序组件时,如果该应用没有正在运行的其它程序组件,那么Android系统将为这个应用创建一个新进程(包含一个线程)用于运行应用。缺省情况下,一个应用的所有...
    99+
    2022-06-06
    进程 线程 Android
  • 详解Android进程和线程
    写在前面的话 一个Android应用就是一个Linux进程,每个应用在各自的进程中运行,互不干扰,比较安全。 一个应用对应一个主线程,就是通常所说的UI线程,android遵守...
    99+
    2022-06-06
    线程 Android
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作