锁与同步机制影响性能的方面
- 竞争开销:多个线程竞争同一把锁时,会产生上下文切换开销。操作系统需要暂停当前持有锁的线程,调度等待锁的线程,这涉及到保存和恢复线程的运行状态,消耗CPU时间。例如在一个高并发的Web服务器应用中,若多个线程频繁竞争数据库连接锁,大量时间将浪费在上下文切换上。
- 死锁风险:如果线程获取锁的顺序不当,可能导致死锁。此时线程相互等待对方释放锁,都无法继续执行,整个程序陷入停滞,这不仅影响当前性能,甚至导致程序崩溃。例如,线程A获取锁1后等待锁2,线程B获取锁2后等待锁1,就形成死锁。
- 锁粒度问题:锁的粒度若设置过大,会导致过多资源被锁定,其他线程长时间等待。比如,一个函数对整个数据结构加锁,即使只需要修改其中一小部分数据,其他线程对该数据结构任何部分的访问都要等待锁释放,降低了并发度。
- 饥饿现象:高优先级线程频繁获取锁,低优先级线程可能长时间无法获取锁,从而得不到执行机会,影响整体系统的公平性和性能。例如在一个多媒体处理程序中,负责实时视频流处理的高优先级线程持续抢占锁,导致负责音频处理的低优先级线程饥饿。
优化方式及示例
- 减小锁粒度:
- 优化思路:将大锁分解为多个小锁,只在访问需要同步的最小数据单元时加锁。
- 示例代码:
import threading
class SmallLockOptimization:
def __init__(self):
self.data1 = 0
self.data2 = 0
self.lock1 = threading.Lock()
self.lock2 = threading.Lock()
def update_data1(self):
with self.lock1:
self.data1 += 1
def update_data2(self):
with self.lock2:
self.data2 += 1
- 合理安排锁获取顺序:
- 优化思路:所有线程按照相同顺序获取锁,避免死锁。
- 示例代码:
import threading
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread_function1():
with lock1:
with lock2:
# 执行需要同步的操作
pass
def thread_function2():
with lock1:
with lock2:
# 执行需要同步的操作
pass
- 使用读写锁:
- 优化思路:对于读多写少的场景,使用读写锁。读操作可以同时进行,写操作需要独占锁。
- 示例代码:
import threading
class ReadWriteLockOptimization:
def __init__(self):
self.data = 0
self.read_lock = threading.Lock()
self.write_lock = threading.Lock()
self.read_count = 0
def read_data(self):
with self.read_lock:
self.read_count += 1
if self.read_count == 1:
self.write_lock.acquire()
# 执行读操作
with self.read_lock:
self.read_count -= 1
if self.read_count == 0:
self.write_lock.release()
def write_data(self):
with self.write_lock:
# 执行写操作
self.data += 1
- 使用信号量替代锁:
- 优化思路:信号量可以控制同时访问资源的线程数量,在一些场景下比锁更灵活。
- 示例代码:
import threading
semaphore = threading.Semaphore(5) # 允许5个线程同时访问
def semaphore_usage():
with semaphore:
# 执行操作
pass