python - 多线程就这样
前言:
讲线程之前,先扯一下进程。
什么是进程?
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。
程序和进程的区别就在于:
- 程序:是指令的集合,它是进程运行的静态描述文本;
- 进程:是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
有进程为什么还要线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。
但是,进程也有缺陷:
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
一个操作系统比喻成一个工厂,每个车间就相当于一个进程,一个车间有多个员工组成,这些员工都可以并行进行生产,这时候车间效率是不是就起来了,每个工人就是线程。
什么是线程?
线程是操作系统能够进行运算调度的最小单位。
它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程和线程的区别:
- 进程是一段正在执行的程序,是资源分配的基本单元,而线程是CPU调度的基本单元。
- 进程间相互独立进程,进程之间不能共享资源,一个进程至少有一个线程,同一进程的各线程共享整个进程的资源(寄存器、堆栈、上下文)。
- 线程的创建和切换开销比进程小。
单线程
先来看看单线程的模式
# coding=utf-8
from time import ctime, sleep # ctime 获取当前时间
def dinner(func):
for i in range(2):
print("I'm having a meal: {}, 时间: {}" .format(func, ctime()))
sleep(1) # 休眠 1 s
def drink(func):
for i in range(2):
print("I'm drinking: {}, 时间: {}" .format(func, ctime()))
sleep(3)
if __name__ == '__main__':
dinner("猪脚饭")
drink("茅台")
print("all over {}" .format(ctime()))
定义两个函数,一个吃饭,一个喝酒。
先看结果:
I'm having a meal: 猪脚饭, 时间: Sat Jan 15 15:45:28 2022
I'm having a meal: 猪脚饭, 时间: Sat Jan 15 15:45:29 2022
I'm drinking: 茅台, 时间: Sat Jan 15 15:45:30 2022
I'm drinking: 茅台, 时间: Sat Jan 15 15:45:33 2022
all over Sat Jan 15 15:45:36 2022
开始是 15:45:28,结束是 15:45:36 ,总耗时 8 s
现在的程序就是单线程模式,吃两口猪脚饭,干两口茅台,顺序执行。
多线程
python多线程官网介绍:
https://docs.python.org/zh-cn/3.7/library/threading.html
以下来修改以下代码,实现一边吃猪脚饭,一边看电影。
# coding=utf-8
import threading
from time import ctime, sleep # ctime 获取当前时间
def dinner(func):
for i in range(2):
print("I'm having a meal: {}, 时间: {}" .format(func, ctime()))
sleep(1) # 休眠 1 s
def movie(func):
for i in range(2):
print("I'm watching movies: {}, 时间: {}" .format(func, ctime()))
sleep(3)
threads = [] # 创建一个空数组
# 使用threading.Thread()方法,target:调用 dinner 方法,args:传参
thread1 = threading.Thread(target=dinner, args=("猪脚饭",))
# 将创建好的线程 thread1 追加到 threads 中
threads.append(thread1)
print() # 换行
thread2 = threading.Thread(target=movie, args=("阿甘正传",))
threads.append(thread2)
if __name__ == '__main__':
# for 循环遍历 threads 数组
for t in threads:
t.setDaemon(True)
# 开始线程活动
t.start()
print()
print("all over {}" .format(ctime()))
setDaemon()
setDaemon(True)将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句
print("all over {}" .format(ctime())) 后,没有等待子线程,直接就退出了,同时子线程也一同结束。
以上代码运行结果:
I'm having a meal: 猪脚饭, 时间: Sat Jan 15 16:18:29 2022
I'm watching movies: 阿甘正传, 时间: Sat Jan 15 16:18:29 2022
all over Sat Jan 15 16:18:29 2022
查看结果,子线程(dinner 和 movie)和主线程 print("all over {}" .format(ctime())) 都是同一时间启动。
设置守护线程之后,当主线程结束时,子线程也将立即结束,不再执行。
主线程等待子线程结束
为了让守护线程(子线程)执行结束之后,主线程再结束,我们可以使用 join 方法,让主线程等待子线程执行。
for t in threads:
t.join()
join()方法的位置是在for循环外,等两个子线程结束后,再执行主进程。
# coding=utf-8
import threading
from time import ctime, sleep # ctime 获取当前时间
def dinner(func):
for i in range(2):
print("I'm having a meal: {}, 时间: {}" .format(func, ctime()))
sleep(1) # 休眠 1 s
def movie(func):
for i in range(2):
print("I'm watching movies: {}, 时间: {}" .format(func, ctime()))
sleep(3)
threads = []
thread1 = threading.Thread(target=dinner, args=("猪脚饭",))
threads.append(thread1)
print() # 换行
thread2 = threading.Thread(target=movie, args=("阿甘正传",))
threads.append(thread2)
if __name__ == '__main__':
for t in threads:
t.setDaemon(True)
t.start()
for t in threads:
t.join()
print()
print("end: {}" .format(ctime()))
先看结果:
I'm having a meal: 猪脚饭, 时间: Sat Jan 15 17:56:59 2022
I'm watching movies: 阿甘正传, 时间: Sat Jan 15 17:56:59 2022
I'm having a meal: 猪脚饭, 时间: Sat Jan 15 17:57:00 2022
I'm watching movies: 阿甘正传, 时间: Sat Jan 15 17:57:02 2022
end: Sat Jan 15 17:57:05 2022
子线程 dinner 和 movie 是同时开始的,17:56:59 开始,直到调用主进程为17:57:05 ,
总耗时 6 s。单线程的时候耗时 8 s,使用多线程耗时减少 2 s。
所以可以看出多线程执行程序会快一些。