Python 魔法学院 – 第18篇:Python 多线程 ⭐⭐⭐
目录
引言
在编程的世界里,时间就是金钱。随着计算机硬件的飞速发展,多核处理器已经成为标配。如何充分利用这些计算资源,提升程序的执行效率,成为了每个开发者必须面对的挑战。Python 作为一门简洁而强大的编程语言,提供了多种并发编程的方式,其中多线程是最常用的一种。
本文将带你深入 Python 多线程的世界,从基础概念到高级应用,逐步揭开多线程编程的神秘面纱。无论你是初学者、中级开发者,还是资深开发者,都能在这里找到适合自己的知识点。
1. 多线程编程基础
1.1 什么是多线程?
多线程(Multithreading)是指在一个进程中同时运行多个线程,每个线程执行不同的任务。线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
详细解释:
比喻:
1.2 为什么需要多线程?
详细解释:
1.3 Python 中的多线程模块
Python 提供了 threading
模块来支持多线程编程。threading
模块是 Python 标准库的一部分,使用起来非常方便。
详细解释:
threading
模块提供了 Thread
类来创建和管理线程。threading
模块还提供了多种同步机制,如 Lock
、RLock
、Condition
等,用于线程间的同步和通信。代码示例:
import threading
def worker():
print("线程开始执行")
for i in range(5):
print(f"线程执行中: {i}")
print("线程执行结束")
# 创建线程实例
thread = threading.Thread(target=worker)
# 启动线程
thread.start()
# 等待线程执行完毕
thread.join()
print("主线程结束")
结果为:
线程开始执行
线程执行中: 0
线程执行中: 1
线程执行中: 2
线程执行中: 3
线程执行中: 4
线程执行结束
主线程结束
解释:
threading.Thread
的 target
参数指定线程要执行的函数。thread.start()
启动线程,线程开始执行 worker
函数。thread.join()
等待线程执行完毕,主线程才会继续执行。2. 创建和启动线程
2.1 使用 threading.Thread
创建线程
在 Python 中,创建线程的最简单方式是使用 threading.Thread
类。我们可以通过继承 threading.Thread
类并重写 run
方法来定义线程的执行逻辑。
代码示例:
import threading
class MyThread(threading.Thread):
def run(self):
print("线程开始执行")
for i in range(5):
print(f"线程执行中: {i}")
print("线程执行结束")
# 创建线程实例
thread = MyThread()
# 启动线程
thread.start()
# 等待线程执行完毕
thread.join()
print("主线程结束")
结果为:
线程开始执行
线程执行中: 0
线程执行中: 1
线程执行中: 2
线程执行中: 3
线程执行中: 4
线程执行结束
主线程结束
解释:
MyThread
类继承自 threading.Thread
,并重写了 run
方法,run
方法定义了线程的执行逻辑。thread.start()
启动线程,线程开始执行 run
方法。thread.join()
等待线程执行完毕,主线程才会继续执行。2.2 使用 target
参数创建线程
除了继承 threading.Thread
类,我们还可以通过传递 target
参数来创建线程。target
参数指定线程要执行的函数。
代码示例:
import threading
def worker():
print("线程开始执行")
for i in range(5):
print(f"线程执行中: {i}")
print("线程执行结束")
# 创建线程实例
thread = threading.Thread(target=worker)
# 启动线程
thread.start()
# 等待线程执行完毕
thread.join()
print("主线程结束")
结果为:
线程开始执行
线程执行中: 0
线程执行中: 1
线程执行中: 2
线程执行中: 3
线程执行中: 4
线程执行结束
主线程结束
解释:
threading.Thread
的 target
参数指定线程要执行的函数。thread.start()
启动线程,线程开始执行 worker
函数。thread.join()
等待线程执行完毕,主线程才会继续执行。3. 线程同步
3.1 为什么需要线程同步?
在多线程环境中,多个线程可能会同时访问共享资源,导致数据不一致或程序行为异常。为了避免这种情况,我们需要使用线程同步机制。
详细解释:
比喻:
3.2 使用 Lock
实现线程同步
Lock
是 Python 中最基本的线程同步机制。它允许我们确保同一时间只有一个线程可以访问共享资源。
代码示例:
import threading
# 共享资源
counter = 0
# 创建锁
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
# 获取锁
lock.acquire()
counter += 1
# 释放锁
lock.release()
# 创建多个线程
threads = []
for i in range(10):
thread = threading.Thread(target=increment)
threads.append(thread)
thread.start()
# 等待所有线程执行完毕
for thread in threads:
thread.join()
print(f"最终结果: {counter}")
结果为:
最终结果: 1000000
解释:
lock.acquire()
获取锁,确保同一时间只有一个线程可以执行 counter += 1
。lock.release()
释放锁,允许其他线程获取锁。counter
的最终结果为 1000000
。3.3 使用 RLock
实现可重入锁
RLock
(可重入锁)允许同一个线程多次获取锁,而不会导致死锁。这在递归函数中非常有用。
代码示例:
import threading
# 共享资源
counter = 0
# 创建可重入锁
rlock = threading.RLock()
def increment():
global counter
for _ in range(100000):
# 获取锁
rlock.acquire()
counter += 1
# 释放锁
rlock.release()
# 创建多个线程
threads = []
for i in range(10):
thread = threading.Thread(target=increment)
threads.append(thread)
thread.start()
# 等待所有线程执行完毕
for thread in threads:
thread.join()
print(f"最终结果: {counter}")
结果为:
最终结果: 1000000
解释:
RLock
允许同一个线程多次获取锁,而不会导致死锁。RLock
非常有用,因为它允许同一个线程多次获取锁。4. 线程间通信
4.1 使用 Queue
实现线程间通信
Queue
是 Python 中用于线程间通信的常用数据结构。它是线程安全的,可以在多个线程之间安全地传递数据。
代码示例:
import threading
import queue
import time
# 创建队列
q = queue.Queue()
def producer():
for i in range(5):
print(f"生产者生产: {i}")
q.put(i)
time.sleep(1)
def consumer():
while True:
item = q.get()
if item is None:
break
print(f"消费者消费: {item}")
q.task_done()
# 创建生产者线程
producer_thread = threading.Thread(target=producer)
# 创建消费者线程
consumer_thread = threading.Thread(target=consumer)
# 启动线程
producer_thread.start()
consumer_thread.start()
# 等待生产者线程执行完毕
producer_thread.join()
# 等待队列中的所有任务完成
q.join()
# 发送结束信号
q.put(None)
# 等待消费者线程执行完毕
consumer_thread.join()
print("主线程结束")
结果为:
生产者生产: 0
消费者消费: 0
生产者生产: 1
消费者消费: 1
生产者生产: 2
消费者消费: 2
生产者生产: 3
消费者消费: 3
生产者生产: 4
消费者消费: 4
主线程结束
解释:
Queue
是线程安全的队列,可以在多个线程之间安全地传递数据。q.put(item)
将数据放入队列。q.get()
从队列中获取数据。q.task_done()
标记任务完成。q.join()
等待队列中的所有任务完成。4.2 使用 Condition
实现线程间通信
Condition
是 Python 中用于线程间通信的高级同步机制。它允许线程等待特定条件满足后再继续执行。
代码示例:
import threading
# 创建条件变量
condition = threading.Condition()
# 共享资源
items = []
def producer():
with condition:
print("生产者开始生产")
items.append("item")
print("生产者生产了一个 item")
condition.notify() # 通知消费者
def consumer():
with condition:
while not items:
print("消费者等待生产")
condition.wait() # 等待生产者通知
print(f"消费者消费了: {items.pop()}")
# 创建生产者线程
producer_thread = threading.Thread(target=producer)
# 创建消费者线程
consumer_thread = threading.Thread(target=consumer)
# 启动线程
consumer_thread.start()
producer_thread.start()
# 等待线程执行完毕
producer_thread.join()
consumer_thread.join()
print("主线程结束")
结果为:
消费者等待生产
生产者开始生产
生产者生产了一个 item
消费者消费了: item
主线程结束
解释:
Condition
允许线程等待特定条件满足后再继续执行。condition.wait()
使线程等待,直到其他线程调用 condition.notify()
或 condition.notify_all()
。condition.notify()
唤醒一个等待的线程。condition.notify_all()
唤醒所有等待的线程。5. 线程池
5.1 使用 ThreadPoolExecutor
创建线程池
ThreadPoolExecutor
是 Python 中用于管理线程池的高级工具。它可以自动管理线程的创建和销毁,简化多线程编程。
代码示例:
from concurrent.futures import ThreadPoolExecutor
import time
def task(n):
print(f"任务 {n} 开始执行")
time.sleep(2)
print(f"任务 {n} 执行结束")
return n * n
# 创建线程池
with ThreadPoolExecutor(max_workers=3) as executor:
# 提交任务
futures = [executor.submit(task, i) for i in range(5)]
# 获取任务结果
for future in futures:
print(f"任务结果: {future.result()}")
print("主线程结束")
结果为:
任务 0 开始执行
任务 1 开始执行
任务 2 开始执行
任务 0 执行结束
任务 3 开始执行
任务 1 执行结束
任务 4 开始执行
任务 2 执行结束
任务 3 执行结束
任务 4 执行结束
任务结果: 0
任务结果: 1
任务结果: 4
任务结果: 9
任务结果: 16
主线程结束
解释:
ThreadPoolExecutor
自动管理线程的创建和销毁。executor.submit(task, i)
提交任务到线程池。future.result()
获取任务的结果。6. 多线程编程的陷阱与注意事项
6.1 GIL(全局解释器锁)
Python 的全局解释器锁(GIL)是一个互斥锁,它确保同一时间只有一个线程执行 Python 字节码。这意味着在多线程程序中,即使有多个 CPU 核心,Python 也无法真正实现并行执行。
详细解释:
代码示例:
import threading
def cpu_bound_task():
result = 0
for _ in range(10**7):
result += 1
print(f"任务完成: {result}")
# 创建多个线程
threads = []
for i in range(4):
thread = threading.Thread(target=cpu_bound_task)
threads.append(thread)
thread.start()
# 等待所有线程执行完毕
for thread in threads:
thread.join()
print("主线程结束")
结果为:
任务完成: 10000000
任务完成: 10000000
任务完成: 10000000
任务完成: 10000000
主线程结束
解释:
multiprocessing
模块)。6.2 死锁
死锁是指两个或多个线程互相等待对方释放锁,导致程序无法继续执行。为了避免死锁,我们应该尽量避免嵌套锁,并确保锁的获取和释放顺序一致。
详细解释:
代码示例:
import threading
# 创建两个锁
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread1():
with lock1:
print("线程1获取了锁1")
with lock2:
print("线程1获取了锁2")
def thread2():
with lock2:
print("线程2获取了锁2")
with lock1:
print("线程2获取了锁1")
# 创建两个线程
t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)
# 启动线程
t1.start()
t2.start()
# 等待线程执行完毕
t1.join()
t2.join()
print("主线程结束")
结果为:
线程1获取了锁1
线程2获取了锁2
(程序卡住,无法继续执行)
解释:
thread1
获取了 lock1
,然后尝试获取 lock2
。thread2
获取了 lock2
,然后尝试获取 lock1
。解决方案:
6.3 线程安全
线程安全是指多个线程同时访问共享资源时,程序的行为是正确的。为了确保线程安全,我们应该使用线程同步机制,如 Lock
、RLock
、Condition
等。
详细解释:
Lock
、RLock
、Condition
等。代码示例:
import threading
# 共享资源
counter = 0
# 创建锁
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
# 获取锁
lock.acquire()
counter += 1
# 释放锁
lock.release()
# 创建多个线程
threads = []
for i in range(10):
thread = threading.Thread(target=increment)
threads.append(thread)
thread.start()
# 等待所有线程执行完毕
for thread in threads:
thread.join()
print(f"最终结果: {counter}")
结果为:
最终结果: 1000000
解释:
counter
。counter
的最终结果可能会小于 1000000
。7. 总结
多线程编程是 Python 并发编程的重要组成部分。通过本文的学习,你应该已经掌握了 Python 多线程编程的基础知识,包括线程的创建与启动、线程同步、线程间通信、线程池等内容。同时,我们也探讨了多线程编程中的一些陷阱与注意事项。
希望本文能够激发你对 Python 多线程编程的兴趣,并帮助你在实际开发中更好地利用多线程技术。如果你有任何问题或建议,欢迎在评论区留言讨论。
作者:码力全開