前言
Python 多线程的应用场景通常是在需要同时执行多个 I/O 密集型任务时,以提高程序的效率和性能。
多线程应用场景
网络爬虫:当需要从多个网站获取数据时,使用多线程可以同时发起多个 HTTP 请求,以加快数据获取速度。
数据库操作:当需要对大量数据进行读取、写入或修改时,使用多线程可以将任务分成多个子任务,每个子任务在不同的线程中执行,以提高数据库操作效率。
图像处理:当需要对大量图像进行处理时,使用多线程可以将处理任务分成多个子任务,每个子任务在不同的线程中处理,以提高图像处理速度。
并发服务器:当需要同时处理多个客户端请求时,使用多线程可以让服务器同时处理多个请求,以提高服务器的吞吐量。
优点
提高程序的响应速度:在执行多个 I/O 密集型任务时,多线程可以并行执行这些任务,从而提高程序的响应速度。
简化编程模型:使用多线程可以将复杂的任务分解成多个子任务,并使用多个线程同时执行这些子任务,使得程序结构更清晰。
资源共享:多线程可以共享同一进程的资源,包括内存空间、文件描述符等,方便数据共享和通信。
适用于 I/O 密集型任务:适合处理需要大量等待 I/O 操作的任务,如网络请求、文件读写等。
缺点
全局解释器锁(GIL):Python 的全局解释器锁会导致多线程无法利用多核 CPU,限制了多线程的
并行性能。线程安全性:多线程编程需要考虑线程安全性问题,如数据竞争、死锁等,增加了程序设计和调试的难度。
可扩展性受限:由于 GIL 的存在,多线程并不能实现真正的
并行执行,因此在 CPU 密集型任务上性能提升有限。调试困难:多线程程序更容易出现并发相关的 bug,调试和定位问题相对困难。
绕不开的瓶颈-全局解释器锁(GIL)
全局解释器锁(Global Interpreter Lock,简称 GIL)是 Python 解释器中的一个机制,用于保护共享资源,即 Python 对象的内存管理。在 CPython 解释器中(即官方的 Python 解释器实现),GIL 的存在限制了同一时刻只有一个线程可以执行 Python 字节码。
线程安全性:由于 Python 的内存管理机制并不是线程安全的,多个线程同时操作 Python 对象可能会导致数据结构损坏或内存泄漏。因此,GIL 通过限制同一时刻只有一个线程执行 Python 字节码,保证了 Python 对象的线程安全。
简化实现:GIL 简化了 CPython 解释器的实现,减少了对内存管理的复杂度,降低了解释器的开发和维护成本。
限制和缺点
性能影响:在 CPU 密集型任务中,GIL 会限制多线程的并发执行,因为同一时刻只有一个线程能够执行 Python 字节码,导致无法充分利用多核 CPU。
阻碍并行性:由于 GIL 的存在,Python 多线程并不能实现真正的并行执行,只能依靠多进程来充分利用多核 CPU。
影响扩展性:对于需要高性能并发的应用,如 Web 服务器、科学计算等,GIL 可能成为性能瓶颈,限制了应用的扩展性。
使用范例
采用多线程
import datetime
import threading
import time
# 定义一个简单的函数,用于在多线程中执行
def task():
thread_name = 1
print(f"Thread {thread_name} is starting")
print(f"Thread {thread_name} is start IO")
time.sleep(3) # 模拟耗时操作
print(f"Thread {thread_name} is finish IO")
print(f"Thread {thread_name} is start 计算")
time.sleep(1) # 模拟耗时操作
print(f"Thread {thread_name} is finish 计算")
print(f"Thread {thread_name} is start 输出")
time.sleep(1) # 模拟耗时操作
print(f"Thread {thread_name} is finish 输出")
print(f"Thread {thread_name} is finishing")
def task2():
thread_name = 2
print(f"Thread {thread_name} is starting")
print(f"Thread {thread_name} is start IO")
time.sleep(1) # 模拟耗时操作
print(f"Thread {thread_name} is finish IO")
print(f"Thread {thread_name} is start 计算")
time.sleep(1) # 模拟耗时操作
print(f"Thread {thread_name} is finish 计算")
print(f"Thread {thread_name} is start 输出")
time.sleep(1) # 模拟耗时操作
print(f"Thread {thread_name} is finish 输出")
print(f"Thread {thread_name} is finishing")
def run():
task()
task2()
t1 = datetime.datetime.now()
# 创建多个线程
thread1 = threading.Thread(target=task)
thread2 = threading.Thread(target=task2)
threads = [thread1, thread2]
# 启动并等待所有线程执行完成
for thread in threads:
thread.start()
for thread in threads:
thread.join()
cost_time = (datetime.datetime.now() - t1).seconds
print(f"All threads have finished cost_time={cost_time}s")
"""
Thread 1 is starting
Thread 1 is start IO
Thread 2 is starting
Thread 2 is start IO
Thread 2 is finish IO
Thread 2 is start 计算
Thread 2 is finish 计算
Thread 2 is start 输出
Thread 2 is finish 输出Thread 1 is finish IO
Thread 1 is start 计算
Thread 2 is finishing
Thread 1 is finish 计算
Thread 1 is start 输出
Thread 1 is finish 输出
Thread 1 is finishing
All threads have finished cost_time=5s
"""
不采用多线程
# 按顺序运行两个任务
run()
cost_time = (datetime.datetime.now() - t1).seconds
print(f"All threads have finished cost_time={cost_time}s")
"""
Thread 1 is starting
Thread 1 is start IO
Thread 1 is finish IO
Thread 1 is start 计算
Thread 1 is finish 计算
Thread 1 is start 输出
Thread 1 is finish 输出
Thread 1 is finishing
Thread 2 is starting
Thread 2 is start IO
Thread 2 is finish IO
Thread 2 is start 计算
Thread 2 is finish 计算
Thread 2 is start 输出
Thread 2 is finish 输出
Thread 2 is finishing
All threads have finished cost_time=8s
"""
采用多线程的前后对比
注意:在使用多线程的测试样例中,CPU的资源只有一个,只要不同时使用就不会产生额外的等待时间.否则,需要用线程锁来控制它们对共同资源的使用权.