问题
进程(Process)是一个具有一定独立功能的程序关于某个数据集合的一次运行活动
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
那么多线程有什么作用呢?
分析
在讲解线程之前,先看看如下一段代码:
from threading import current_thread
import time
def func1(name):
print(f'当前线程:{current_thread()}') # 查看当前运行的线程
print(f'Threading:{name} start')
time.sleep(2)
print(f'Threading:{name} end')
if __name__ == '__main__':
print(f'当前线程:{current_thread()}')
start = time.time() # 起始时间
func1('t1')
func1('t2')
end = time.time() # 结束时间
print(f'运行时间:{end - start}')
print('主线程结束!')
运行结果如下:
current_thread() 可以查看当前程序所在的线程,由此可以看到,从开始到结束,就只有一个主线程 MainThread。func1('t1')
和 func1('t2')
这两个函数运行时是串行的,所以运行的时间总共需要 4 秒左右。
可是如果当我们除了一个主线程之外,每次调用 func() 函数都开一个子线程呢?运行时间会有什么变化?
再看看以下代码:
from threading import Thread, current_thread
import time
def func1(name):
print(f'当前线程:{current_thread()}') # 查看当前运行的线程
print(f'Threading:{name} start')
time.sleep(2)
print(f'Threading:{name} end')
if __name__ == '__main__':
print(f'当前线程:{current_thread()}')
# 创建线程
t1 = Thread(target=func1, args=('t1',))
t2 = Thread(target=func1, args=('t2',))
start = time.time()
# 开始线程
t1.start()
t2.start()
# func1('t1')
# func1('t2')
end = time.time()
print(f'运行时间:{end - start}')
print('主线程结束!')
咱们把原来调用 func() 函数的代码先注释掉,同时对应每次调用 func() 函数开启两个子线程,线程的执行统一通过 start() 方法,Thread() 方法中第一个参数传入要调用的函数名,第二个参数传入调用函数需要传入的参数,用元组包装,再来看看运行结果:
添加两个线程后,运行时间是变快了,不过真有这么快吗,另外主线程结束了,两个子线程竟然还没结束!这肯定不是我们想要的结果…
那如果在开始线程之前加上两句代码:t1.setDaemon(True)
和 t2.setDaemon(True)
,那么结果又会怎样呢?
# 开始线程
t1.setDaemon(True)
t1.start()
t2.setDaemon(True)
t2.start()
运行结果:
我们看到,主线程结束之后,所有子线程也同时结束。
线程在分法有:
- 主线程:程序的本身
- 子线程:在程序另开起的线程
注意:在行为上还有一种叫守护线程,主要的特征是它的生命周期。主线程死亡,它也就随之死亡
不过虽然主线程和子线程都同时结束了,不过两个子线程是被强制结束的,并没有运行完成,所以还没有达到我们的要求,那有什么方法真正满足我们的需求呢?革命尚未成功,同志还需努力!
这个方法就是使用 join() 方法:
- 主线程不会等待子线程运行结束,如果需要等待可使用 join() 方法
- 不要启动线程后立即 join(),很容易造成串行运行,导致并发失效
示例代码:
from threading import Thread, current_thread
import time
def func1(name):
print(f'当前线程:{current_thread()}') # 查看当前运行的线程
print(f'Threading:{name} start')
time.sleep(2)
print(f'Threading:{name} end')
if __name__ == '__main__':
print(f'当前线程:{current_thread()}')
# 创建线程
t1 = Thread(target=func1, args=('t1',))
t2 = Thread(target=func1, args=('t2',))
start = time.time()
# 开始线程
t1.start()
t2.start()
# 等待线程
t1.join()
t2.join()
# func1('t1')
# func1('t2')
end = time.time()
print(f'运行时间:{end - start}')
print('主线程结束!')
再来运行下看下结果:
所有线程运行完只花了 2 秒左右的时间,说明两个子线程实现了并发运行,与我们的需求相符。
多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一 样。当然,真正地同时执行多线程需要多核 CPU 才可能实现。
解决
Python 的标准库提供了两个模块:_thread 和 threading,_thread 是低级模块, threading 是高级模块,对_thread 进行了封装。绝大多数情况下,我们只需要使用 threading 这个高级模块来创建线程。
线程的创建有两种方式:
- 方法包装
- 类包装
方法包装
多线程可以通过循环开启:
from threading import Thread, current_thread
import time
def func1(name):
print(f'当前线程:{current_thread()}') # 查看当前运行的线程
print(f'Threading:{name} start')
time.sleep(2)
print(f'Threading:{name} end')
if __name__ == '__main__':
print(f'当前线程:{current_thread()}')
# 创建线程
t1 = Thread(target=func1, args=('t1',))
t2 = Thread(target=func1, args=('t2',))
start = time.time()
# 循环创建多个线程
thread_list = []
for i in range(10):
t = MyThread(f't{i + 1}')
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
end = time.time()
print(f'运行时间:{end - start}')
print('主线程结束!')
运行结果:
当前线程:<_MainThread(MainThread, started 8236)>
当前线程:<Thread(Thread-1, started 8476)>
Threading:t1 start
当前线程:<Thread(Thread-2, started 18236)>
Threading:t2 start
当前线程:<Thread(Thread-3, started 15412)>
Threading:t3 start
当前线程:<Thread(Thread-4, started 15532)>
Threading:t4 start
当前线程:<Thread(Thread-5, started 10044)>
Threading:t5 start
当前线程:<Thread(Thread-6, started 12060)>
Threading:t6 start
当前线程:<Thread(Thread-7, started 1824)>
Threading:t7 start
当前线程:<Thread(Thread-8, started 6292)>
Threading:t8 start
当前线程:<Thread(Thread-9, started 11008)>
Threading:t9 start
当前线程:<Thread(Thread-10, started 13488)>
Threading:t10 start
Threading:t1 end
Threading:t2 end
Threading:t4 end
Threading:t3 end
Threading:t7 end
Threading:t6 end
Threading:t8 end
Threading:t10 end
Threading:t9 end
Threading:t5 end
运行时间:2.0024218559265137
主线程结束!
类包装
自定义 MyThread 类继承自 Thread 类,初始化方法要继承父类的初始化方法,实例方法的名称必须为 run。
from threading import Thread, current_thread
import time
# 类实现-多线程
class MyThread(Thread):
def __init__(self, name):
super(MyThread, self).__init__()
self.name = name
def run(self):
print(f'当前线程:{current_thread()}')
print(f'Threading:{self.name} start')
time.sleep(2)
print(f'Threading:{self.name} end')
if __name__ == '__main__':
print(f'当前线程:{current_thread()}')
start = time.time()
# 循环创建多个线程
thread_list = []
for i in range(10):
t = MyThread(f't{i + 1}')
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
end = time.time()
print(f'运行时间:{end - start}')
运行结果:
当前线程:<_MainThread(MainThread, started 10004)>
当前线程:<MyThread(t1, started 5816)>
当前线程:<MyThread(t2, started 18444)>
Threading:t2 start
当前线程:<MyThread(t3, started 17092)>
Threading:t3 start
当前线程:<MyThread(t4, started 14200)>
Threading:t4 start
Threading:t1 start
当前线程:<MyThread(t5, started 12076)>
Threading:t5 start
当前线程:<MyThread(t6, started 11204)>
Threading:t6 start
当前线程:<MyThread(t7, started 8044)>
Threading:t7 start
当前线程:<MyThread(t8, started 14776)>
Threading:t8 start
当前线程:<MyThread(t9, started 15060)>
Threading:t9 start
当前线程:<MyThread(t10, started 4904)>
Threading:t10 start
Threading:t5 end
Threading:t6 end
Threading:t4 end
Threading:t2 end
Threading:t3 end
Threading:t1 end
Threading:t9 end
Threading:t10 end
Threading:t8 end
Threading:t7 end
运行时间:2.0033164024353027