python 多线程编程之进程和线程基础概念

时间:2022-10-23 04:46:50

多线程编程

在多线程(multithreaded,MT)出现之前,计算机程序的执行都是由单个步骤序列组成的,该序列组合在主机的CPU中按照同步顺序执行。无论是任务本身需要按照步骤顺序执行,还是整个过程实际上包含了多个子任务,都需要按照既定顺序方式执行。那么,如果这些子任务相互独立,没有因果关系呢,我们是不是考虑要让这些独立的任务同时进行呢?这种并行处理方式可以显著地提高整个任务的性能。这就是多线程编程。

由以上我们可以推测一下,多线程编程针对具有如下特点的编程任务是非常理想的:本质上是异步的;需要多个并发任务;每个活动的处理顺序可能是不确定的。这种编程任务可以被组织成或者划分成多个执行流,其中每个执行流都有一个指定要完成的任务。根据应用的不同,这些子任务可能需要计算出中间结果,然后合并成最终的输出结果。

计算密集型的任务可以比较容易的划分为多个子任务,而那种使用单线程处理多个外部输入源的任务就不那么简单了。如果不使用多线程,要实现这种编程任务就需要为串行程序使用一个或多个计时器,并实现一个多路复用方案。一个串行程序需要从每个IO终端通道来检查用户的输入,然而,程序在读取IO终端通道时不能阻塞,因为用户输入的到达时间是不确定的,并且阻塞回复妨碍其他IO通道的处理。串行程序必须使用非阻塞的IO或拥有计时器的阻塞IO。串行程序只有唯一的执行线程,因此他必须兼顾要执行的多个任务,确保其中的某个任务不会占用过多时间,并对用户的响应时间进行合理的分配。要想实现类似的要求,往往会造成非常复杂的控制流,难以理解和维护。

多线程编程,以及类似Queue(一种多线程队列数据结构)的共享数据机构,可以把任务规划为几个执行特定函数的线程:

  • UserRequestThread:负责读取用户端的输入。程序将创建多个线程,每个客户端一个,客户端的请求将会被放入队列中。
  • RequestProcessor:该线程负责从队列中获取请求并处理,为下一步的线程提供输出。
  • ReplyThread:负责向用户输出,将结果传给用户,或者把数据写到本地文件系统或者数据库中

这个设计中,每个线程的逻辑都不复杂,他们都只需要一个要完成的特定作业。你只需要设计每类线程去做一件事情就可以了。

进程:

计算机程序就是存储在磁盘上的可执行的二进制文件。只有当它们被加载到内存中并被操作系统调用,才拥有生命周期。进程(有时又叫重量级进程)则是一个执行中的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他一些用于追踪执行的数据。操作系统管理者其上所有进程的执行,并尽量为它们分配时间。进程还可以通过派生新的进程来执行其他任务(不同的操作系统上叫法不同,Linux上交fork)。注意的是,每个新进程也都拥有自己的内存和数据栈等,进程之间是通过进程间通信(IPC)的方式实现信息的共享。

线程:

一般提到线程(又叫轻量级进程)的时候,默认的前提是它们都是在同一个进程下执行的,并享有相同的上下文。可以将其认为是在一个主进程或者‘主线程’中并行运行的一些‘迷你线程’

线程一般包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下文。当其他线程运行时,它可以被抢占(中断)和临时挂起(睡眠)。这种方式叫让步(yielding)。

同一个进程中的各个线程和主线程共享同一片数据空间,所以,相比于进程而言,线程间的通信和信息共享更能容易实现;线程一般是并发执行的。综合这两点,使得多任务间的协作成为可能。注意的是,单核CPU的前提下,真正的并发是不可能,线程一般都是这么执行的:每个线程执行一段时间,然后让步给其他线程;每个线程都有自己的任务,必要的时候,可以和其他线程进行结果通信。

另外,有共享就有风险。由于多个线程可以访问同一片数据,由于数据访问的顺序不同,可能导致结果不一致的现象。这种情况成为竟太条件(race condition)。解决方法是,大部分语言都有一些同步原语,用于线程管理器控制执行和访问。

还有就是,线程无法给与公平的执行时间。因为有些函数在完成前保持阻塞状态,如果没有专门为多线程情况进行修改,会导致CPU的时间分配像这些贪婪的函数倾斜。

总结:

线程是最小的执行单元,而进程至少由一个线程组成。如何调度进程和线程,由操作系统决定。

多进程和多线程编程,涉及到同步、数据共享的问题,需要多花功夫。