1.简单了解下:
线程,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,一个进程可以运行多个线程,线程是进程中的实际运作单位,一条线程指的是进程中的一个单一顺序的控制流。但是,python中的多线程,由于GIL(全局解释器锁)的存在,并不是真正的并行,工作模式是线程间不断的、快速的切换来完成任务,同一时间只能有一个线程工作。
2.进程和线程的关系,举个例子说明:
一个进程就像一台车,它是一个容器,有不同的功能,车本身并没有主动地做任何的事情,而线程可以理解成使用车的人,他可以踩油门,可以踩刹车,可以打方向盘,看到这里,有人可能会说,我可以一边踩油门一边打方向盘啊,同时进行,可是在python里,存在GIL,你只能在同一时间干一件事。
所以只能在IO密集型的任务中使用多线程才有意义,比如,你打开一个文件,需要2秒,你有2个文件需要打开,如果不使用多线程,你打开这2个文件,一共需要4秒,使用多线程,只需要2秒。过程如下:
线程1获取GIL的使用权,打开文件1,因为需要等待2秒,所以在等待的时候,交出了GIL的使用权;线程2,获得使用权,打开文件2,等待2秒,因为在这个切换中非常快,感受不到这种切换,觉得是同时打开了2个文件,共耗时2秒。
实现多线程的方式有2种,一种是使用调用threading模块的Thread, 一种是通过构建多线程类(继承Thread)来实现。
方法一,调用Thread来实现多线程。
1.demo 1
import time
from threading import Thread, current_thread
def demo():
print(f'子线程开始, 线程名字: {current_thread().name}')
time.sleep(2) # 模拟等待操作, 模拟耗时的io任务
# current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
print(f'子线程结束, 线程名字: {current_thread().name}')
if __name__ == '__main__':
print('主线程开始...')
threads = [Thread(target=demo) for _ in range(3)] # 这里是创建3个线程,放到一个列表里
for t in threads:
t.start() # 启动线程
for t in threads:
t.join() # 线程等待
print('主线程结束...')
输出如下:
这里解释下, t.start()是启动线的方法, t.join()是线程等待的意思(阻塞调用这个方法的线程(主线程是调用子线程的方法)),在这里用t.join(),主线程会等所有的子线程完成后再结束,因为每一个子线程都启用了t.join();
疑问一:t.join()可以跟t.start()放在同一循环内吗?
答案是不可以,如果放在一个循环里,每启动一个子线程t.start后,就马上做t.join,这个时候,启动的这个子线程是强占资源的,抢到这个资源后,必须要等启动的这个子线程完成后,才能启动下一个子线程,
也就是说,这个处理是阻塞的,没有起到多线程的效果,是线性运行的。效果如下
疑问二:如果不使用join可以吗?
答案是可以的,但是,在实际使用中不会这样用,主线程结束后,子线程就是放养状态,不知道什么时候才结束掉,不方便管理。
效果如下:
另外,join可以设置等待时间,使用参数timeout,timeout设置的时间到了后,主线程得到使用权,主线程结束。
方法二,继承Thread来实现多线程。其实就是重写Thread里面的run方法来实现多线程
# _*_ coding: utf-8 _*_
import time
from threading import Thread, current_thread
class Demo(Thread):
def run(self):
print(f'子线程{self.name}开始...') # 继承Thread后, 可以调Thread的方法name来获取当前线程名字
time.sleep(2)
print(f'子线程{self.name}结束...')
if __name__ == '__main__':
print('主线程开始...')
threads = [Demo() for _ in range(3)] # 创建3个线程对象
for t in threads:
t.start()
for t in threads:
t.join()
print('主线程结束...')