python 线程(一)理论部分

2023-01-31 00:01:25 python 线程 理论

  进程有很多优点,它提供了多道编程,可以提高计算机CPU的利用率。既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的。

主要体现在一下几个方面:

  • 进程只能在一个时间做一个任务,如果想同时做两个任务或多个任务,就必须开启多个进程去完成多个任务。
  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
  • 每个进程都有自己的独立空间,所以多进程的创建,销毁相比于多线程更加耗时,也更加占用系统资源。

进程是资源分配的最小单位,线程是CPU调度的最小单位,每一个进程中至少有一个线程。 

线程与进程的区别

可以归纳为以下4点:

  1)地址空间:进程间相互独立的每个进程都有自己独立的内存空间,也就是说一个进程内的数据在另一个进程是不可见的。但同一进程中的各线程间数据是共享的。

  2)通信:由于每个进程有自己独立的内存空间,所以进程间通信需要IPC,而进程内的数据对于多个线程来说是共享的,每个线程都可以访问,所以为了保证数据的一致性,需要使用

  3)调度和切换:线程上下文切换比进程上下文切换要快得多。

  4)在多线程操作系统中,进程不是一个可执行的实体,它主要的功能是向操作系统申请一块内存空间,然后在内存空间中开线程来执行任务,相当于一个容器,容器中的线程才是真正的执行体。一个进程可以包含多个线程,而一个线程是不能包含进程的。因为进程是系统分配资源的最小单位,所以线程不能向操作系统申请自己的空间,但一个线程内可以包含多个线程。

线程的特点:

  在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。

1)轻型实体

  线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。

  线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。

2)独立调度和分派的基本单位。

  在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。

3)共享进程资源。

  在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

4)可并发执行

在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。

 线程的实现可以分为两类:

用户级线程(User-Level Thread)和内核级线程(Kernel-Level Thread),后者又称为内核支持的线程或轻量级进程。在多线程操作系统中,各个系统的实现方式并不相同,在有的系统中实现了用户级线程,有的系统中实现了内核级线程。 

用户线程和内核线程的区别:

1 内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
2 用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
3 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
4 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
5 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。

内核线程的优缺点:

优点:当有多个处理机时,一个进程的多个线程可以同时执行。

缺点:由内核进行调度。

用户线程的优缺点:

优点:
线程的调度不需要内核直接参与,控制简单。
可以在不支持线程的操作系统中实现。
创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。
允许每个进程定制自己的调度算法,线程管理比较灵活。
线程能够利用的表空间和堆栈空间比内核级线程多。
同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。
缺点:
资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用

全局解释器锁GIL(Cpython)

 Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。CPython在执行多线程的时候并不是线程安全的,所以为了程序的稳定性,加一把全局解释锁,确保任何时候都只有一个Python线程执行。虽然 Python 解释器中可以“运行”多个线程,但在同一时刻只有一个线程在解释器中运行。

我们所说的Python全局解释锁(GIL)简单来说就是一个互斥体(或者说锁),这样的机制只允许一个线程来控制Python解释器。GIL对执行单线程任务的程序员们来说并没什么显著影响,但是它成为了计算密集型(CPU-bound)和多线程任务的性能瓶颈。

  在多线程环境中,Python 虚拟机按以下方式执行:

  a、设置 GIL;

  b、切换到一个线程去运行;

  c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

  d、把线程设置为睡眠状态;

  e、解锁 GIL;

  d、再次重复以上所有步骤。
  在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

Python多线程模块的选择:

Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构
  避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。 

  thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。

相关文章