跳到主要内容

线程同步机制

介绍

在多线程编程中,多个线程可能会同时访问共享资源(如变量、文件或数据结构)。如果不对这些访问进行控制,可能会导致数据不一致或程序行为异常。线程同步机制就是用来确保多个线程在访问共享资源时能够协调工作,避免冲突。

线程同步的核心目标是保证线程安全,即确保多个线程在并发执行时,程序的逻辑和数据的完整性不会受到破坏。

为什么需要线程同步?

假设有两个线程同时对一个共享变量进行写操作,如果没有同步机制,可能会导致以下问题:

  1. 竞态条件(Race Condition):多个线程同时修改共享资源,导致最终结果依赖于线程的执行顺序。
  2. 数据不一致:一个线程修改了数据,而另一个线程读取了未更新的数据。

为了解决这些问题,我们需要使用线程同步机制。


常见的线程同步机制

以下是几种常见的线程同步机制:

1. 互斥锁(Mutex)

互斥锁是最常用的同步机制之一。它确保同一时间只有一个线程可以访问共享资源。当一个线程获得锁时,其他线程必须等待锁被释放后才能继续执行。

代码示例

以下是一个使用互斥锁的 Python 示例:

python
import threading

# 共享资源
counter = 0
# 创建互斥锁
lock = threading.Lock()

def increment():
global counter
for _ in range(100000):
lock.acquire() # 获取锁
counter += 1
lock.release() # 释放锁

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

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

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

print(f"Final counter value: {counter}")

输出:

Final counter value: 200000
备注

如果没有使用互斥锁,counter 的最终值可能会小于 200000,因为两个线程可能会同时修改 counter


2. 信号量(Semaphore)

信号量是一种更通用的同步机制,它允许一定数量的线程同时访问共享资源。信号量维护一个计数器,当计数器大于 0 时,线程可以访问资源;否则,线程必须等待。

代码示例

以下是一个使用信号量的 Python 示例:

python
import threading

# 共享资源
counter = 0
# 创建信号量,允许最多 2 个线程同时访问
semaphore = threading.Semaphore(2)

def increment():
global counter
for _ in range(100000):
semaphore.acquire() # 获取信号量
counter += 1
semaphore.release() # 释放信号量

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

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

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

print(f"Final counter value: {counter}")

输出:

Final counter value: 200000
提示

信号量适用于需要限制同时访问资源的线程数量的场景,例如连接池或资源池。


3. 条件变量(Condition Variable)

条件变量用于线程间的通信。它允许线程在某些条件满足时继续执行,否则进入等待状态。通常与互斥锁一起使用。

代码示例

以下是一个使用条件变量的 Python 示例:

python
import threading

# 共享资源
queue = []
# 创建条件变量和互斥锁
condition = threading.Condition()

def producer():
global queue
for i in range(5):
with condition:
queue.append(i)
print(f"Produced: {i}")
condition.notify() # 通知消费者
threading.Event().wait(1) # 模拟生产延迟

def consumer():
global queue
while True:
with condition:
while not queue:
condition.wait() # 等待生产者通知
item = queue.pop(0)
print(f"Consumed: {item}")
if item == 4: # 退出条件
break

# 创建生产者和消费者线程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

# 启动线程
producer_thread.start()
consumer_thread.start()

# 等待线程完成
producer_thread.join()
consumer_thread.join()

输出:

Produced: 0
Consumed: 0
Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
Produced: 3
Consumed: 3
Produced: 4
Consumed: 4
警告

条件变量通常用于生产者-消费者模型,确保消费者在资源可用时才执行操作。


实际应用场景

  1. 数据库连接池:使用信号量限制同时访问数据库连接的线程数量。
  2. 多线程文件处理:使用互斥锁确保多个线程不会同时写入同一个文件。
  3. 任务队列:使用条件变量实现生产者-消费者模型,协调任务的生成和处理。

总结

线程同步机制是多线程编程中不可或缺的一部分。通过互斥锁、信号量和条件变量等工具,我们可以有效地管理共享资源的访问,避免竞态条件和数据不一致问题。

注意

过度使用同步机制可能会导致性能问题(如死锁或线程饥饿),因此需要根据实际需求合理选择同步方法。


附加资源


练习

  1. 修改上面的互斥锁示例,尝试去掉锁,观察 counter 的最终值是否发生变化。
  2. 使用信号量实现一个简单的资源池,限制同时访问资源的线程数量。
  3. 实现一个生产者-消费者模型,使用条件变量协调线程间的通信。

通过实践这些练习,你将更深入地理解线程同步机制的工作原理和应用场景。