本文所依赖的环境为:
进程和线程的概念
进程概念
我们想运行一个程序,首先会将该程序从存储介质上通过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
来做线程的实例化对象,我们需要传入target
和args
,target
需要传入函数名称,args
需要传入参数,注意这里参数需要传入可迭代的对象,所以当只有一个参数的时候,也需要在后面加一个,
。最后使用start
方法让其开始执行。
运行后的效果如下:
可以发现,我们程序几乎同步的打印下载开始,也几乎同时打印下载完毕。
总结起来发现,我们线程启动一个线程,是不是非常简单呢?只需要引入threading
模块,定义Thread
来做对象,最后使用start()
运行即可。
线程阻塞也很重要
线程直接跑就完了呗,为什么还需要阻塞呢?我们这里做一个简单的需求:上面的代码改改,我们下载完视频后,要压缩一下,由于只讨论线程,所以就只用print
代替操作,我们可以这样操作:
在上面的基础上,我们增加了2个步骤,1: 是将下载好的文件放入列表fileList
中,2. 最后开始遍历fileList
文件,进行压缩,最后打印一个压缩完毕,看起来没什么问题吧?
那我们来运行一下一下呢?
额。。。这个很显然不符合我们的预期,我们还没下载文件完毕,怎么就开始压缩了呢,而且压缩完毕了,再输出的文件下载完毕,这是为什么呢?
这是因为线程在启动后,如果我们不去设置阻塞,他就会一直执行下去,就拿我们刚才的案例来看,我们可以将其理解为:
看上图,我们启动线程后,它就放在后台了,我们就立马执行遍历fileList
步骤,但是这个时候恰恰fileList
是空的,所以我们压缩了空文件,压缩完毕后,文件才下载完毕。
这个时候我们就需要等线程执行完毕之后,再执行下面的语句了,否则执行完了没意义,所以这个时候就需要引入线程阻塞了,我们需要将下载的线程全部执行完毕后,再开始压缩文件,只需要线程增加一个join
方法即可,代码修改如下:
在原先的基础上,我们需要先定义一个线程池,用于放已经执行了的线程,而后再遍历该线程池,每一个都设置阻塞,这样就会等所有线程都执行完毕了,再进行后面的操作,由于我们后面是压缩需要用到前面的结果,所以阻塞是必不可少的,程序执行结果为:
这个流程图可以理解为这样的:
现在你知道阻塞有什么用了吧。
总结
今天介绍了一下python
的多线程,这里只是简单的使用threading
库,在python
中,多线程的库不仅于此,还有thread
、Queue
等。最后举了一个很简单的例子来说明线程阻塞的重要性。