Python 并发编程:线程详解

在 Python 中,默认每个 Python 文件会创建一个进程,用于提供运行资源和环境。进程启动后,会创建一个主线程,用于执行程序代码。

线程是并发编程的核心之一,本文将详细解析 Python 中线程的基础概念、常见操作、线程安全以及线程池的使用。


一、线程基础

1. 什么是线程?

线程是计算机中可以被 CPU 调度的最小单位。每个线程在程序中独立运行,多个线程可以并发执行。在 Python 中,线程由 threading 模块提供支持。


2. 创建线程

Python 提供了非常简单的方式来创建线程,可以通过 threading.Thread 类来定义和启动线程。

示例:创建一个线程

import threading

def func(a1, a2, a3):
    print(f"Arguments received: {a1}, {a2}, {a3}")

# 创建线程并传入目标函数和参数
t = threading.Thread(target=func, args=(11, 22, 33))
t.start()  # 启动线程
t.join()   # 等待线程完成

二、多线程开发

在多线程编程中,主线程从上到下执行代码。当需要创建子线程时,主线程会调用子线程并继续向下执行。如果程序运行结束前子线程未完成任务,主线程会进入等待状态,直到所有子线程执行完毕。

1. 线程的常见方法

以下是线程对象的一些常用方法:

  1. t.start()
    启动线程,使其进入就绪状态,等待 CPU 调度。线程的执行时间由操作系统决定。

  2. t.join()
    主线程等待当前线程执行完毕后再继续执行后续代码。通常用于保证线程的同步。

  3. t.setDaemon(布尔值)
    设置线程的守护状态:

    • True:设置为守护线程,当主线程结束时,子线程会自动退出。
    • False(默认):设置为非守护线程,主线程会等待子线程完成后再退出。
  4. 线程名称的设置和获取
    可以通过 name 参数设置线程名称,或使用 threading.current_thread().name 获取当前线程的名称。

示例:线程名称

import threading

def my_func():
    print('Thread name is', threading.current_thread().name)

# 创建线程并设置名称
my_thread = threading.Thread(target=my_func, name='MyThread')
my_thread.start()

2. 自定义线程类

可以通过继承 threading.Thread 类,自定义线程行为,将线程需要执行的逻辑写入 run() 方法中。

示例:定义一个自定义线程类

import threading
import time

class MyThread(threading.Thread):
    def __init__(self, name, counter):
        super().__init__()
        self.name = name
        self.counter = counter

    def run(self):
        print(f"Starting {self.name}")
        while self.counter:
            print(f"{self.name}: {self.counter}")
            self.counter -= 1
            time.sleep(1)
        print(f"Exiting {self.name}")

# 创建线程实例
thread1 = MyThread("Thread-1", 5)
thread2 = MyThread("Thread-2", 3)

# 启动线程
thread1.start()
thread2.start()

# 等待线程完成
thread1.join()
thread2.join()

print("Exiting Main Thread")

三、线程安全

在多线程环境中,多个线程可能会操作同一资源(如全局变量、文件等)。这可能导致数据竞争和不一致的问题。通过同步机制(如锁)来保护共享资源可以确保线程安全。

1. 互斥锁(Lock)

互斥锁是一种最简单的锁机制,同一时刻只允许一个线程访问共享资源。

示例:使用互斥锁保护共享资源

import threading

lock = threading.Lock()
shared_var = 0

def increment():
    global shared_var
    for _ in range(100000):
        with lock:  # 使用上下文管理自动加锁和解锁
            shared_var += 1

def decrement():
    global shared_var
    for _ in range(100000):
        with lock:
            shared_var -= 1

# 创建两个线程
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=decrement)

# 启动线程
thread1.start()
thread2.start()

# 等待线程完成
thread1.join()
thread2.join()

# 打印共享变量的值
print(shared_var)  # 期望输出:0

2. 可重入锁(RLock)

可重入锁允许同一线程多次申请锁,而不会导致死锁。

示例:使用可重入锁

import threading

lock = threading.RLock()
shared_var = 0

def process():
    global shared_var
    with lock:  # 第一次加锁
        with lock:  # 第二次加锁
            shared_var += 1

threads = [threading.Thread(target=process) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(shared_var)

3. 事件锁(Event)

事件锁用于线程间的通信,可以控制某些线程在特定事件发生后再继续执行。

示例:线程等待事件发生

import threading
import time

event = threading.Event()

def worker():
    print("Worker is waiting for event...")
    event.wait()  # 等待事件发生
    print("Worker is running...")

# 创建线程
thread = threading.Thread(target=worker)
thread.start()

# 主线程设置事件
time.sleep(2)
event.set()  # 通知事件发生
thread.join()

4. 条件变量锁(Condition)

条件变量锁用于线程间的复杂通信,线程可以在满足某些条件时被唤醒。

示例:生产者-消费者问题

import threading
import time

cond = threading.Condition()
queue = []

def producer():
    for i in range(5):
        time.sleep(1)
        with cond:
            queue.append(i)
            print(f"Produced: {i}")
            cond.notify()  # 通知消费者

def consumer():
    while True:
        with cond:
            while not queue:
                cond.wait()  # 等待生产者通知
            item = queue.pop(0)
            print(f"Consumed: {item}")

# 创建线程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

producer_thread.start()
consumer_thread.start()

producer_thread.join()

四、线程池

创建过多线程可能导致资源浪费和效率下降。线程池能够有效限制线程数量,动态分配线程执行任务。

示例:使用线程池

from concurrent.futures import ThreadPoolExecutor

def task(x, y):
    return x + y

# 创建线程池,最多维护 5 个线程
pool = ThreadPoolExecutor(5)

# 提交任务
future = pool.submit(task, 3, 4)
print(f"Task result: {future.result()}")  # 输出:7

# 提交多个任务
futures = [pool.submit(task, i, i+1) for i in range(10)]
for f in futures:
    print(f.result())  # 获取每个任务的结果

# 关闭线程池
pool.shutdown()

五、总结

Python 的 threading 模块提供了强大的线程支持,但由于全局解释器锁(GIL)的存在,Python 的多线程在处理 CPU 密集型任务时效率有限。适当使用锁机制和线程池,可以有效提高多线程程序的性能和资源利用率。

优化建议:

  • I/O 密集型任务:可以使用多线程加速执行。
  • CPU 密集型任务:建议使用多进程(multiprocessing 模块)或结合异步编程。

你可能感兴趣的:(Python进阶知识,python,开发语言)