python线程--threading模块

1. threading模块简介

在 Python 中,多线程是一种非常常用的并发编程方式,可以实现多个线程同时执行,通常用于处理一些 I/O 密集型的任务,比如网络通信、文件读写等操作。Python 的 threading 模块就是专门用于实现多线程的库,提供了许多与线程相关的函数和类。

1.1 threading模块的基本用法

threading 模块主要包括以下几个类:Thread、Lock、RLock、Condition、Semaphore 和 Event。其中,Thread 是最常用的类,用于创建线程对象,其他几个类则用于线程同步。

下面是一个简单的 threading 模块使用例子,创建了两个线程,分别输出数字 0 到 9:

import threading

def print_num():

for i in range(10):

print(i)

t1 = threading.Thread(target=print_num)

t2 = threading.Thread(target=print_num)

t1.start()

t2.start()

可以看到,上面的代码创建了两个线程,分别启动后并行运行,输出数字 0 到 9。

1.2 线程同步

在多线程编程中,因为多个线程可能同时访问同一个共享的资源,可能会产生一些问题,比如数据竞争(Data Race)、死锁(Deadlock)等等。为了避免这些问题,Python 提供了一些同步机制,使得多个线程能够在访问共享资源时协调彼此的行为。

1.2.1 Lock 和 RLock

Lock(互斥锁)是一个最基本的同步原语,用于保护共享资源的访问。在多个线程同时访问共享资源时,Lock 可以确保每一时刻只有一个线程可以访问该资源。RLock(可重入锁)则是一种特殊的 Lock,支持同一线程多次获取锁而不会死锁的情况。

下面是一个使用 Lock 来同步的例子,实现了一个简单的计数器,保证每一次自增操作都是原子性的:

import threading

class Counter:

def __init__(self):

self._value = 0

self._lock = threading.Lock()

def increment(self):

with self._lock:

self._value += 1

print(self._value)

counter = Counter()

t1 = threading.Thread(target=counter.increment)

t2 = threading.Thread(target=counter.increment)

t1.start()

t2.start()

可以看到,上述代码使用了 Lock 保护了自增操作,每次只有一个线程可以进入 increment 函数并完成自增操作,从而避免了数据竞争问题。

1.2.2 Condition

Condition(条件变量)是一种复杂的同步机制,允许线程在满足特定条件之前挂起等待,一旦条件满足则可以唤醒挂起的线程。它是基于 Lock 和 Event 的,因此可以与 Lock 和 RLock 配合使用。

下面是一个使用 Condition 来同步的例子,实现了一个阻塞队列,当队列为空时消费者线程会等待直到队列不为空:

import threading

import queue

class BlockingQueue:

def __init__(self):

self._queue = queue.Queue()

self._condition = threading.Condition()

def put(self, item):

with self._condition:

self._queue.put(item)

self._condition.notify()

def get(self):

with self._condition:

while self._queue.empty():

self._condition.wait()

return self._queue.get()

def producer(queue):

for i in range(10):

queue.put(i)

def consumer(queue):

while True:

item = queue.get()

print(item)

queue = BlockingQueue()

t1 = threading.Thread(target=producer, args=(queue,))

t2 = threading.Thread(target=consumer, args=(queue,))

t1.start()

t2.start()

可以看到,上述代码使用了 Condition 保护了队列操作,当队列为空时,消费者线程会调用 wait 方法来陷入等待状态,直到生产者线程往队列中添加新元素并唤醒等待线程。

1.2.3 Semaphore 和 Event

Semaphore(信号量)和 Event(事件)也是常用的同步原语,它们都可以用于多个线程之间的协调与同步。

Semaphore 是一种计数器,用于控制并发访问的数量。在多个线程同时执行的情况下,Semaphore 可以确保并发访问的数量不超过某一个阈值,从而避免了一些资源抢占的问题。下面是一个使用 Semaphore 来同步的例子,表示同时只能允许两个线程访问资源:

import threading

semaphore = threading.Semaphore(2)

def do_work():

with semaphore:

# 访问共享资源

pass

Event 则是一种通信机制,用于线程之间的事件通知与等待。Event 对象提供了两种方法:set 和 clear。set 方法会将 event 对象的内部状态设置为 True,而 clear 方法则会将状态设置为 False。wait 方法用于等待事件的发生,只有当事件状态为 True 时才会返回,否则会一直阻塞直到超时或收到中断信号。

下面是一个使用 Event 来同步的例子,实现了生产者与消费者模式:

import threading

event = threading.Event()

queue = []

def producer():

for i in range(10):

queue.append(i)

event.set()

event.clear()

def consumer():

while True:

event.wait()

item = queue.pop(0)

print(item)

t1 = threading.Thread(target=producer)

t2 = threading.Thread(target=consumer)

t1.start()

t2.start()

上述代码中,生产者线程向队列中添加元素后,会调用 event.set() 方法来通知消费者线程开始工作。消费者线程在执行完打印操作后,会调用 event.clear() 方法将事件状态重新设置为 False,等待下次的通知。

2. GIL 与多线程

在 Python 中,有一个重要的概念就是 GIL(Global Interpreter Lock,全局解释器锁),它是一个线程级别的锁,用于保护 Python 解释器中的共享内存数据结构。GIL 在 Python 中起到了一个重要的作用,它可以确保同一时刻只有一个线程可以执行 Python 字节码,从而避免了并发访问共享数据结构时的竞争问题。

然而,GIL 在多线程编程中也存在一些问题。因为 Python 解释器中只有一个线程可以执行 Python 字节码,所以在多个线程同时执行 CPU 密集型任务时,效率并不会得到提高。与此同时,GIL 也会导致一些线程同步的问题,比如死锁、饥饿(Starvation)等等。

因此,在 Python 中,多线程主要是针对 I/O 密集型任务进行优化的,即那些需要等待外部操作才能继续运行的任务。这些任务使用多线程可以让 Python 程序更加高效,因为在等待外部操作的时候,单个线程会被阻塞,而其他线程可以继续执行。

3. 总结

本文介绍了 Python 中的 threading 模块,包括了基本用法、线程同步的机制以及 GIL 对多线程的影响。Python 中的多线程编程适用于 I/O 密集型任务,能够提高程序的效率,但在 CPU 密集型任务中效果不尽如人意。在实际编程过程中,需要根据任务类型和需求来选择适合的并发编程方式,才能发挥出最佳的性能和效果。

后端开发标签