跳转至

一、基础锁类型

  1. 互斥锁(Mutex)
    用于确保同一时刻仅有一个线程访问共享资源。
    特点:简单易用,但可能导致线程阻塞和饥饿问题。
    实现示例:C++的 std::mutex、Java的 synchronized 关键字、Python的 threading.Lock

  2. 读写锁(Read-Write Lock)
    允许多个线程同时读取资源,但写入时独占访问。
    适用场景:读操作远多于写操作(如缓存系统)。
    实现示例:Java的 ReentrantReadWriteLock、C++的 std::shared_mutex

  3. 自旋锁(Spinlock)
    线程在获取锁失败时循环检查锁状态,而非阻塞。
    适用场景:锁持有时间极短且避免线程切换的开销(如高频短任务)。
    注意:长时间自旋可能导致CPU资源浪费。

  4. 递归锁(Recursive Lock)
    允许同一线程多次获取同一把锁,避免递归函数中的死锁。
    实现示例:C++的 std::recursive_mutex、Java的 ReentrantLock

  5. 信号量(Semaphore)
    通过计数器控制资源访问数量,支持多线程并发访问。
    适用场景:连接池、限流等。

  6. 条件变量(Condition Variable)
    用于线程间协作,结合互斥锁实现特定条件的等待与通知(如生产者-消费者模型)。


二、锁的分类标准

  1. 悲观锁 vs 乐观锁
    悲观锁:假定并发冲突高,直接加锁(如互斥锁、读写锁)。
    乐观锁:假定冲突低,通过版本号或CAS(Compare-And-Swap)机制检测冲突(如数据库的MVCC)。

  2. 公平锁 vs 非公平锁
    公平锁:按请求顺序分配锁(如 ReentrantLock(true))。
    非公平锁:允许插队获取锁(默认策略,性能更高)。

  3. 可重入锁 vs 不可重入锁
    可重入锁:同一线程可多次获取锁(如 synchronizedReentrantLock)。
    不可重入锁:仅允许单次获取(如早期的 Mutex)。


三、其他高级锁机制

  1. StampedLock(优化读写锁)
    C++和Java中提供,支持乐观读锁,减少锁竞争。
  2. CountDownLatch/CyclicBarrier
    用于线程同步(如等待多个任务完成)。
  3. Exchanger
    允许两个线程在特定点交换数据(如管道通信)。

四、选择锁的考量因素

  1. 锁粒度:粗粒度锁降低并发性,细粒度锁增加复杂度。
  2. 持有时间:尽量减少锁的持有时间以降低竞争。
  3. 性能与资源消耗:自旋锁适合短任务,互斥锁适合长任务。
  4. 错误处理:需处理锁获取失败(如超时、中断)。

五、常见问题与优化

死锁:避免方法包括按顺序获取锁、设置超时(如 tryLock)。
活锁:通过随机退避或优先级调度解决。
锁优化:锁降级(写锁转读锁)、锁拆分(减少竞争范围)。

临界区

一、临界区的核心特性

  1. 互斥访问
    同一时刻仅允许一个线程进入临界区,其他线程必须等待当前线程退出后才能访问。例如,若两个线程同时修改同一全局变量,未加保护的临界区可能导致数据损坏。

  2. 关联临界资源
    临界资源指被共享且需互斥使用的资源(如打印机、共享变量等),临界区则是操作这些资源的代码段。例如,对共享数组的读写操作必须通过临界区保护。

  3. 有限时间停留
    进入临界区的线程需在有限时间内完成操作并退出,否则会导致其他线程无限等待,引发性能问题或资源饥饿。


二、临界区的管理原则

为确保安全性和效率,临界区需满足以下要求: 1. 互斥性:同一时刻仅一个线程可进入相关临界区。 2. 有限等待:线程等待进入临界区的时间必须有上限。 3. 非阻塞性:无法进入临界区的线程应释放CPU资源,避免“忙等”(如通过挂起或让出CPU)。 4. 独立性:不相关的临界区(操作不同资源)可并行执行。


三、临界区的实现机制

  1. 互斥锁(Mutex)
    通过锁定和解锁操作实现互斥。例如,C++的 std::mutex 或 Linux的 pthread_mutex_lock(),线程进入临界区前加锁,退出时解锁。

  2. 信号量(Semaphore)
    使用计数器控制并发访问数量。二元信号量(初始值为1)可等效为互斥锁。例如,数据库连接池可通过信号量限制同时访问的线程数。

  3. 条件变量(Condition Variable)
    结合互斥锁实现线程间协作。例如,生产者-消费者模型中,消费者线程在缓冲区空时等待条件变量触发。

  4. 原子操作与无锁编程
    通过硬件支持的原子指令(如CAS)直接操作共享资源,避免锁的开销。适用于高频短任务,但实现复杂度较高。


四、临界区的应用注意事项

  1. 最小化临界区范围
    仅保护必须互斥的代码段,减少锁持有时间。例如,避免在临界区内执行I/O操作或用户输入等待。

  2. 避免死锁与活锁
    按固定顺序获取锁,设置超时机制(如 tryLock),或使用死锁检测算法。

  3. 性能权衡
    锁机制可能引入上下文切换或CPU自旋开销。例如,自旋锁适合短临界区,而互斥锁适合长任务。


五、示例场景

线程安全计数器:通过互斥锁保护全局计数器变量的自增操作,确保多线程下的结果正确(如代码示例)。 • 共享缓冲区同步:使用条件变量协调生产者和消费者线程,防止缓冲区溢出或读空。


总结

临界区是并发编程中保护共享资源的核心机制,其设计需平衡安全性、性能和复杂度。开发者需根据具体场景选择同步原语(如互斥锁、信号量等),并遵循最小化保护范围、避免阻塞等最佳实践。