python不等待线程执行完成_1


进程与线程

基本背景1-CPU多任务执行

计算机由CPU、各类寄存器、RAM、各种资源(硬盘等)组成,CPU速度极快只有寄存器的速度可与之匹配,其余设备无法望其项背。因此在等待其他设备返回结果时(如:文件IO操作)CPU可以切换到下一个任务来充分利用原本串行执行中CPU等待的时间,看起来就像是同时进行多个任务。

基本背景2-程序执行

执行一段代码或任务前,除了CPU外全部相关资源必须已经就位。所有这样的任务都处于就绪队列中,然后由操作系统的调度算法选出某个任务,随后PC指针指向该任务的代码开始位置,由CPU开始取指令,然后执行。

基本背景3-执行环境

上面所说的除了CPU外全部相关资源就是任务或程序的执行环境,其主要是寄存器的一些内容,这些就构成了的任务的上下文环境。

进程的上下文是进程执行的环境。当程序执行完毕,或CPU时间片用完了,那它就要被切换出去,等待下一次被CPU执行。在被切换出去做的主要工作就是保存该进程上下文,因为这是下一次被CPU执行的运行环境,必须保存。

进程与线程的区别

简而言之的话,进程(进程实体是程序段+相关数据+PCB)是操作系统资源分配的基本单位,线程是CPU调度的基本单位。进程的出现是为了对计算机资源(CPU资源、存储资源,也就是进程上下文)的一个隔离,而线程的出现是为了解决进程拥有独立的虚拟内存空间后带来的很难对资源并发访问的问题。例如: 看电影,就必须由一个线程播放视频,另一个线程播放音频。

由此可见,进程作为操作系统资源分配的基本单位,其目标是拥有独立的地址空间和其它资源。而如果在一个进程中需要多个任务并发就需要线程。多个线程共享进程的上下文,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)。多线程切换开销较小且由于共享所属进程的全部资源,因此可方便的进行通信。

区别总结

  1. 一个程序至少有一个进程,一个进程至少有一个线程。
  2. 进程是操作系统资源分配的基本单位,在执行过程中拥有独立的内存单元,而多个线程共享内存(仅有自己的栈和栈指针,程序计数器等寄存器)。
  3. 线程不能够独立执行,必须依存在进程中。
  4. 对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。进程间的通信需要通过操作系统提供的管道等相关方式,较为繁琐。

Python并行

多线程

目前来说python使用解释器是官方CPython,解释器中有GIL全局锁,简而言之就是不管CPU是否多核,单位时间内还有一个python线程持有GIL,也就是同时刻多核CPU只能跑一个线程,然后时间片轮转。

但是如果开启了多线程,如果该线程执行系统IO操作(会调用内建的操作系统程序)则会在这个I/O调用之前释放持有的GIL。也就是虽然python多线程无法利用多核并发,但在执行IO密集型任务时是可以被视为多线程并发的。

多线程实现

Python实现多线程方式有很多种,这里仅介绍python线程池执行器实现。


# 创建线程池执行器


as_completed返回线程任务执行结果生成器,主进程在此会等待全部线程执行完毕。

多进程

当面对计算密集型任务想使用python利用多核并行计算,此时可使用多进程,总体来说可以根据任务的不同情况将两者相结合。

多进程实现

直接使用多进程对象


pw


进程死锁

使用多进程往往需要聚合全部子进程计算结果后继续程序的执行。这里可以使用multiprocessing中Queue,Queue实际上利用操作系统的多进程通信管道。因此该队列容量非常有限。如果队列满,则写队列进程会持续阻塞:


# 写进程


这里本想先将数据放入队列后,主进程再去取,但是由于写进程的写入内容太大使得无法完成写入(num=1则程序运行正常)。同时,主进程调用写进程的join方法,等待写进程结束,从而造成进程死锁。

解决方法就是在调用p.join()前执行print(q.get())。主进程调用队列q的取数据方法开始提取队列管道中的数据,此时写进程可将剩余字节流写入管道。

进程池

可以利用python已有的进程池创建进程。


from


这里需注意:如果进程池的多进程间需要使用共享队列,必须使用multiprocessing中的 Manager().Queue()创建进程共享队列,同样的,如果多进程任务中有需要同步的部分需要在主进程中使用Manager().Queue()创建同步状态锁传递给各个子进程(由于进程间内存隔离,每个子进程独立拥有一个锁对象但是这些锁的状态相同,这里应该还涉及到进程间锁状态同步机制)

生产者消费者模型

在并发场景中,生产者消费者模型是最常见的模型。


# 写数据进程执行的代码:


注意:这里由于消费者代码中有死循环不断获取消费队列的数据因此需要显示的通知机制。