众所周知,受限于Python的GIL问题。真正放在CPU中运行的Python线程还是只有一个。因此所谓的多线程可以简单理解为多个任务线程,在一个CPU内核上快速切换造成的假象。这与Golang中那种真多线程(多个groutine并行运行在多个CPU内核上)还是有本质区别的。但是这并不是说Python中的多线程就没用,关键还是看使用场景:

场景一:繁重的数据汇总和计算(即CPU密集型)的任务,如数据清洗

场景二:频繁的网络访问、抓取数据(网络密集型)的任务,如网络爬虫

很明显,第一个场景CPU从头忙到尾,就没有一刻是清闲的。肯定是同时工作的CPU越多效率越高,Python的假象就不好使了。第二个场景虽然网络访问很频繁,但是抓取回来的数据确并不难处理,即不会对cpu形成太多工作量,主要消耗的是等待的时间。这时候Python的多线程就效果很明显。

Python中自带了一个threading模块,实现多线程运行程序。

import threading
import time

loop_count = 5


def count_num(num):
    for x in range(10):
        # 随着循环次数增加等待时间
        time.sleep(x)
        num = num + x
    return num


print('单线程开始')
start = time.time()
# 简单循环5次,count_num函数
for i in range(loop_count):
    count_num(i)
end = time.time()

print('单线程结束')
ret = end - start
print('单线程耗时: {}'.format(ret))

# 定义一个多线程的任务列表
task_list = []
for i in range(loop_count):
    # 实例化线程任务,把count_num函数的运行交给子线程运行
    task = threading.Thread(target=count_num, args=(i,))
    task_list.append(task)

print('-'*20)
print('多线程开始')
start = time.time()
for task in task_list:
    # 将任务取出来,调用start()方法真正开始运行子线程
    task.start()

for task in task_list:
    # 顺序检测已经start的子线程是否还在运行
    if task.is_alive():
        # hold住主线程,等待当前存活的子线程运行结束才能继续向下运行。
        task.join()
        
print('多线程结束')
end = time.time()
ret = end - start
print('多线程耗时: {}'.format(ret))

运行时间对比,在等待时间较多的场景下多线程可以节约大量的时间。

单线程开始
单线程结束
单线程耗时: 225.1616358757019
--------------------
多线程开始
多线程结束
多线程耗时: 45.03864336013794