浅析Python中线程以及线程阻塞

Python
315
0
0
2023-05-12
目录
  • 进程和线程的概念
  • 进程概念
  • 线程概念
  • 线程优缺点
  • 在python中如何使用线程
  • 线程阻塞也很重要
  • 总结

本文所依赖的环境为:

进程和线程的概念

进程概念

我们想运行一个程序,首先会将该程序从存储介质上通过IO总线加载进内存中,而后再通过cpu进行调度。这个时候,我么么将这个正在运行的程序称之为进程,它有内存地址、内存空间、数据栈等等信息,进程之间通信一般称之为IPC,常见的方法有 管道、消息队列、套接字等。

线程概念

而线程则不同,线程是在进程中运行的,一个进程至少有一个线程。在单个cpu中,同一时刻一个进程只有一个线程在工作,其他则被挂起,也称之为睡眠。由于线程属于进程,所以会共享进程的内存信息。线程之间通信不仅可以使用共享内存来通信,依然可以使用 如 管道、消息队列、套接字等。

线程优缺点

多线程是一种并发方式,优点为可以同时执行多个任务,用于提升时间和效率。

比如,我们想写一个python服务器下载电影,一次只能下载一部,若我们使用多线程后,可以一次"同时"下载n部,从而提升了效率。

但是有些事多线程不能做的,并发冲突是其中一种。比如掘金点赞功能,如果没有对点赞这个变量进行并发控制,可能会出现数据不一致的情况。

在python中如何使用线程

在使用python写多线程之前,先来看一个小案例,假设我们使用python写了一个下载电影的程序。

import time

def downloadMovie(i):
    print("%s 开始下载编号为%s电影中。。。" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), str(i)))
    time.sleep(5)
    print("%s 编号为%s电影下载完毕" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), str(i)))

def main():
    for i in range(5):
        downloadMovie(i)

if __name__ == '__main__':
    main()

我们假设模拟下载电影的一个程序,下载过程使用time.sleep代替。在没有使用多线程的时候,它的执行过程如下:

可以看到,它是顺序执行的,需要等上一部下载完毕,才能开始下载下一部。

如果使用线程来做该需求呢? 我们可以这样来写:

import time
import threading

def downloadMovie(i):
    print("%s 开始下载编号为%s电影中。。。" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), str(i)))
    time.sleep(5)
    print("%s 编号为%s电影下载完毕" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), str(i)))

def main():
    for i in range(5):
        t = threading.Thread(target=downloadMovie,args=(i,))
        t.start()

if __name__ == '__main__':
    main()

上述代码,在原始代码基础上,我们引入了threading库,在循环中使用Thread来做线程的实例化对象,我们需要传入targetargstarget需要传入函数名称,args需要传入参数,注意这里参数需要传入可迭代的对象,所以当只有一个参数的时候,也需要在后面加一个,。最后使用start方法让其开始执行。

运行后的效果如下:

可以发现,我们程序几乎同步的打印下载开始,也几乎同时打印下载完毕。

总结起来发现,我们线程启动一个线程,是不是非常简单呢?只需要引入threading模块,定义Thread来做对象,最后使用start()运行即可。

线程阻塞也很重要

线程直接跑就完了呗,为什么还需要阻塞呢?我们这里做一个简单的需求:上面的代码改改,我们下载完视频后,要压缩一下,由于只讨论线程,所以就只用print代替操作,我们可以这样操作:

在上面的基础上,我们增加了2个步骤,1: 是将下载好的文件放入列表fileList中,2. 最后开始遍历fileList文件,进行压缩,最后打印一个压缩完毕,看起来没什么问题吧?

那我们来运行一下一下呢?

额。。。这个很显然不符合我们的预期,我们还没下载文件完毕,怎么就开始压缩了呢,而且压缩完毕了,再输出的文件下载完毕,这是为什么呢?

这是因为线程在启动后,如果我们不去设置阻塞,他就会一直执行下去,就拿我们刚才的案例来看,我们可以将其理解为:

看上图,我们启动线程后,它就放在后台了,我们就立马执行遍历fileList步骤,但是这个时候恰恰fileList是空的,所以我们压缩了空文件,压缩完毕后,文件才下载完毕。

这个时候我们就需要等线程执行完毕之后,再执行下面的语句了,否则执行完了没意义,所以这个时候就需要引入线程阻塞了,我们需要将下载的线程全部执行完毕后,再开始压缩文件,只需要线程增加一个join方法即可,代码修改如下:

在原先的基础上,我们需要先定义一个线程池,用于放已经执行了的线程,而后再遍历该线程池,每一个都设置阻塞,这样就会等所有线程都执行完毕了,再进行后面的操作,由于我们后面是压缩需要用到前面的结果,所以阻塞是必不可少的,程序执行结果为:

这个流程图可以理解为这样的:

现在你知道阻塞有什么用了吧。

总结

今天介绍了一下python的多线程,这里只是简单的使用threading库,在python中,多线程的库不仅于此,还有threadQueue等。最后举了一个很简单的例子来说明线程阻塞的重要性。