python 之 进程与线程区别、GIL锁产生背景及对Python性能的影响?python的多线程是假的,为啥还用多线程

一、进程与线程区别
  • 根本区别
    进程是操作系统资源分配的基本单位。每个进程都有自己独立的地址空间、数据、堆栈和状态
    线程是处理器任务调度和执行的基本单位。一个进程可以有多个线程,这些线程共享进程的地址空间和资源
  • 资源开销(内存分配)
    进程创建新的进程需要分配独立的地址空间,有较大的字节。进程间切换和通信成本相对较高
    线程间共享地址空间和资源,因此线程的创建、切换和通信相对较接近,资源消耗较少
  • 通信方式
    进程间通信相对复杂,主要方式有管道、消息队列、信号量、共享内存等
    线程间通信更简单,可以直接访问其他线程的数据(因为它们共享地址空间),主要通过锁、信号量等同步原语来协调(也可以共享变量,共享内存,共享数据库,消息队列)
  • 隔离性与安全性:
    进程间有最大的隔离性,一个进程崩溃(例如访问了内存)一般不会影响其他进程。
    线程间共享进程资源,一个线程的错误可能影响同一进程的其他线程。
  • 使用场景
    IO密集型使用线程,计算密集型使用多进程
    进程适用于相互独立,需要较高安全性和隔离性的场景。
    线程适用于需要密集通信,或共享大量资源的场景,如服务器的请求处理
  • 补充
    协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。
    协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
二、什么是GIL锁?GIL对Python性能的影响
  • GIL全局解释锁
    全局解释锁: 每个线程在执行过程的过程都需要先获取GIL,确保在同一时刻只有一个线程可以执行字节码,目的是简化CPython的设计, 保证线程安全

  • 线程释放GIL锁的情況:

    1. 在I0操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL
    2. Python 3.x使用计时器(执行时间达到國值后,当前线程释放GIL)或Python 2.x, tickets计t数达到100

    python使用多进程是可以利用多核的CPU资源的。
    多线程爬取比单线程性能有提升,因为遇到I0阻塞会自动释放GIL锁
    多进程适合在 CPU 密集型操作(Cpu 操作指令比较多,如位数多的浮点运算)。
    多线程适合在 IO 密集型操作(读写数据操作较多的,比如爬虫)

  • GIL全局解释锁:目的是简化CPython的设计, 保证线程安全

    1. 保护内存管理:
      GIL保证了对Python对象的访问是线程安全的。Python使用引用计数来管理内存,而更新引用计数并不是一个原子操作。假如两个线程同时修改同一个对象的引用计数,可能会导致引用计数被错误地更新,进而引发内存泄漏或错误地回收了仍在使用的对象。GIL确保了每一时刻只有一个线程在执行Python字节码,从而避免了这种竞争条件
    2. 简化设计和实现:
      通过使用GIL,CPython解释器的实现大大简化。GIL消除了多线程环境中复杂的锁管理和同步问题。没有GIL,CPython的每个数据结构都需要单独管理锁,这将增加设计和实现的复杂性
三、为啥python的多线程是假的,还用多线程
  • 为什么说Python的多线程是“假”的:
    GIL是一个全局锁,它保证一次只有一个线程执行Python字节码。在多核CPU环境中,这意味着Python的多线程不能有效利用多个CPU核心。相反,尽管有多个线程,但在任何给定的时刻,只有一个线程在执行。这使得Python的多线程对于计算密集型任务效果不佳,因为它们无法真正地执行任务。
  1. 为什么仍然使用Python的多线程
    对于I/O密集型任务(如网络通信、文件I/O等),多线程仍然是非常有效的。
    在I/O密集型任务中,线程大部分时间都在等待外部资源
    比如等待文件读取写完成或网络响应。在此期间,CPU几乎处于空闲状态。
    通过使用多线程,当一个线程等待I/O操作完成时,GIL可以被释放,其他线程可以继续执行。
    这样,多线程可以在I /O密集型应用中提高程序的总体性能和响应能力
  2. 综上所述
    尽管Python的多线程因GIL的存在而受到了一定的限制(特别是对于计算密集型任务),它仍然在某些场景下是有用和有效的。
    选择是否使用多线程,以及如何使用,取决于具体的应用场景和性能需求。
三、为啥 IO密集型使用线程,计算密集型使用多进程
3.1 I/O密集型任务:

I/O密集型任务是指系统的性能瓶颈主要出现在I/O操作上,如文件操作、网络通信等

为什么使用线程??

  1. I/O 密集型任务中,CPU 的使用并不是瓶颈,主要时间消耗在等待 I/O 操作上。在此期间,CPU 大部分时间都是空闲的。
  2. 线程的创建和思考成本很小,且所有线程共享进程的内存空间,使得线程之间的通信和数据交换成本很低。
  3. 使用线程可以使一个进程内的多个线程在等待I/O操作时可以共享同一个资源,这样当一个线程在等待I/O时,其他线程可以继续执行
3.2 计算密集型任务:

计算密集型任务是指系统的性能瓶颈主要出现在CPU计算上,即任务需要大量的CPU计算时间

为什么使用多进程??

  1. 对于计算密集型任务,多进程可以有效地利用多核CPU的优势,因为每个进程可以在一个单独的CPU核上执行。
    进程间的独立性较强,一个进程的崩溃不会影响到其他进程。
    在解释全局器锁(GIL)的存在的环境下(例如CPython),多线程程序无法有效利用多核CPU。GIL是Python的一个互斥锁,它阻止多个线程同时执行Python字节码。
    密集型任务,这可能导致程序的效率大幅度降低。多进程则参与GIL的限制,每个进程都有自己的GIL和独立的内存空间,因此可以充分利用多核CPU。
四、进程间8种通信方式详解
  1. 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常
    是指父子进程关系
  2. 消息队列通信:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载
    无格式字节流以及缓冲区大小受限等缺点
  3. 信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源
    时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  4. 信号:信号是种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  5. 共享内存通信:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内
    存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实来实现进程间的同步和通信。
  6. socket:套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信
为什么爬虫使用多线程?而不是多进程
  • 多进程占用资源多,进程间通讯不方便,爬虫很少用
  • 多线程/协程 在请求数据和解析数据的时候采用多线程,大部分时间都是单线程爬虫,大部分情况采用分布式
什么是并发和并行?.同步和异步,阻塞和非阻塞的区别?

# 并发:同一时刻只能处理一个任务,但可以交替处理多个任务。(一个处理器同时处理多个任务〕
# 并行:同一时刻可以处理多个任务。(多个处理器或者是多核的处理器同时处理多个不同的任务)
# 类比:并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。

线程是并发,进程是并行;进程之间相互独立,是系统分配资源的最小单位,同一个线程中的所有线程共享资源。

阻塞:在执行一个操作时,不能做其他操作;
非阻塞:在执行一个操作时,能做其他操作。

阻塞和非阻塞关心的问题是:能不能做其他操作。

猜你喜欢

转载自blog.csdn.net/a6864657/article/details/132355440