2.1 模块 threading
Python中的多线程是同时运行多个线程 (threads)的一种方式,这样可以更有效地利用CPU资源,特别是在I/O操作或其他可以并行执行的任务中。
Python多线程的基本概念:
线程是一个最小的执行单元,可以与同一进程中的其他线程并行工作。一个进程中的所有线程共享内存,这使得线程之间的数据交换成为可能。
进程是操作系统中执行的程序实例,拥有自己的地址空间和资源。与线程不同,进程相互隔离,通过进程间通信 (IPC)进行数据交换。
GIL 是Python解释器中的机制,防止多个Python线程同时执行。GIL确保Python代码执行的安全性,但限制了多核处理器上多线程程序的性能。
重要! 请注意,由于Global Interpreter Lock (GIL),Python中的多线程可能无法显著提高对计算密集型任务的性能,因为GIL阻止多个Python线程在多核处理器上同时执行。
模块 threading
Python中的模块 threading提供了用于线程操作的高级接口。它允许创建和管理线程,进行线程同步及其之间的交互。让我们更详细地了解这个模块的关键组件和功能。
模块 threading 的主要组件
用于线程操作的实体:
-
Thread— 创建和管理线程的主要类。 -
Timer— 用于在指定时间后执行函数的计时器。 -
ThreadLocal— 创建线程本地数据。
线程同步机制:
-
Lock— 用于防止同时访问共享资源的同步原语。 -
Condition— 用于更复杂的线程同步的条件变量。 Event— 用于线程之间通知的原语。-
Semaphore— 限制可以同时执行某一部分的线程数量的原语。 -
Barrier— 同步设定数量的线程,阻塞它们直到所有的线程到达屏障。
下面我会讲解3个用于线程操作的类,线程同步机制你暂时用不到。
2.2 类 Thread
类 Thread 是用于创建和管理线程的主要类。它有4个主要方法:
start(): 启动线程执行。-
join(): 当前线程暂停并等待已启动线程完成。 is_alive(): 如果线程正在执行则返回True。-
run(): 包含将在线程中执行的代码的方法。在从类Thread继承时被重写。
比想象的要简单得多——以下是使用类 Thread 的示例。
启动简单线程
import threading
def worker():
print("Worker thread is running")
# 创建新线程
t = threading.Thread(target=worker) #创建了一个新的线程对象
t.start() #启动线程
t.join() # 等待线程结束
print("Main thread is finished")
调用 start 方法后,worker 函数将开始执行。更准确地说,它的线程将被添加到活动线程列表中。
使用参数
import threading
def worker(number, text):
print(f"Worker {number}: {text}")
# 创建带参数的新线程
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()
为新线程传递参数,只需将它们作为元组指定并赋值给参数args。当调用 target 中指定的函数时,参数将自动传递。
重写 run 方法
import threading
class MyThread(threading.Thread):
def run(self):
print("Custom thread is running")
# 创建并启动线程
t = MyThread()
t.start()
t.join()
有两种方法指定函数来开始新的线程执行——可以在创建 Thread 对象时通过 target 参数传递,也可以从类 Thread 继承并重写 run 函数。两种方式都被认为是合法的并且经常使用。
2.3 类 Timer
模块 threading 中的类 Timer 用于在指定时间后启动一个函数。这个类对于在多线程环境中执行延迟任务非常有用。
计时器通过要调用的函数和延迟时间(以秒为单位)创建和初始化。
- 方法
start()启动计时器,计时器在计时指定的时间后调用指定的函数。 - 方法
cancel()可以在计时器触发前停止计时器。这对于防止不需要的函数调用很有用。
使用示例:
启动带延迟的函数
在这个示例中,函数 hello 会在计时器启动后5秒被调用。
import threading
def hello():
print("Hello, world!")
# 创建计时器,5秒后调用hello函数
t = threading.Timer(5.0, hello)
t.start() # 启动计时器
在执行前停止计时器
这里计时器将在 hello 函数执行之前被停止,因此不会有任何输出。
import threading
def hello():
print("Hello, world!")
# 创建计时器
t = threading.Timer(5.0, hello)
t.start() # 启动计时器
# 在执行前停止计时器
t.cancel()
带参数的计时器
在这个示例中,计时器将在3秒后调用函数 greet 并传递参数"Alice"。
import threading
def greet(name):
print(f"Hello, {name}!")
# 创建带参数的计时器
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
类 Timer 对于计划在特定时间后任务的执行非常有用。然而,计时器不保证绝对精确的执行时间,因为这取决于系统负载和线程调度器的操作。
2.4 类 ThreadLocal
类 ThreadLocal 旨在创建具有自己本地数据的线程。这在多线程应用中非常有用,当每个线程需要自己的版本数据以避免冲突和同步问题时。
每个使用 ThreadLocal 的线程将拥有自己的独立数据副本。存储在 ThreadLocal 对象中的数据对每个线程都是唯一的,并且不会与其他线程共享。这对于存储仅在单个线程上下文中使用的数据非常方便,例如web应用中的当前用户或数据库的当前连接。
使用示例:
基本使用
在这个示例中,每个线程将其自己的名字分配给本地变量 value 并输出。value 的值对每个线程都是唯一的。
import threading
# 创建 ThreadLocal 对象
local_data = threading.local()
def process_data():
# 分配线程本地变量的值
local_data.value = threading.current_thread().name
# 访问线程本地变量
print(f'Value in {threading.current_thread().name}: {local_data.value}')
threads = []
for i in range(5):
t = threading.Thread(target=process_data)
threads.append(t)
t.start()
for t in threads:
t.join()
在Web应用中存储用户数据
在这个示例中,每个线程处理其自己的用户请求。user_data.user 的值对每个线程是唯一的。
import threading
# 创建 ThreadLocal 对象
user_data = threading.local()
def process_request(user):
# 分配线程本地变量的值
user_data.user = user
handle_request()
def handle_request():
# 访问线程本地变量
print(f'Handling request for user: {user_data.user}')
threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
t = threading.Thread(target=process_request, args=(user,))
threads.append(t)
t.start()
for t in threads:
t.join()
这就是 threading 库中最有用的3个类。你很可能会在工作中使用它们,而其他类就不太可能使用了。现在大家都转向了异步函数和库 asyncio。我们接下来会一直谈论这个。
GO TO FULL VERSION