Thread-如何从整体上理解线程同步方式之一锁(线程并发同步方式之锁概述)

引言

在并发编程中,锁是一种基本的同步机制,用于控制多个线程或进程对共享资源的访问。本文将深入探讨锁的概念、实现方式及其在不同场景下的应用.

概念

锁是一种同步机制,用于在多线程环境中协调对共享资源的访问。它通过限制资源的并发访问,防止数据竞争和不一致性。就是在某些范围内实现同步/有序,这个范围可以是大范围,也可以小范围。实现同步可以阻塞,也可以非阻塞。实现同步在软件方面依赖标志位,或者在硬件方面依赖指令原语

锁的实现方式

软件实现

    标志位

    • 实例头中的标志位:例如Java对象中,对象头中包含锁状态标志,用于表示对象的锁定状态。
    • LCB锁控制块中的标志位:锁控制块(Lock Control Block, LCB)是一种数据结构,用于存储锁的元数据,如锁的状态和持有者。
    • 散列表中的标志位:在某些系统中,锁的状态可能存储在散列表中,以支持更细粒度的锁定。
    • 混合模型:结合上述方法,以适应不同的应用场景。

    范围

    • 大范围锁定:锁定整个对象或数据结构,适用于资源较大或访问频率较低的场景。
    • 小范围锁定:锁定对象的特定部分或数据结构的单个元素,适用于资源较小或访问频率较高的场景。
    • 中范围锁定:介于大范围和小范围之间,适用于需要平衡性能和资源利用率的场景。

    阻塞与非阻塞

    • 自旋等待:线程在等待锁的过程中持续检查锁的状态,适用于锁持有时间短的场景。
    • 休眠等待:线程在等待锁的过程中进入休眠状态,适用于锁持有时间长的场景。

    算法

    • 具体算法:每种算法都可以解决不同的问题,这里不过多讨论

硬件实现

指令原语

硬件级别的锁实现依赖于特定的指令原语,这些原语由处理器提供,用于原子性地操作内存中的值。

指令原语示例

  • CAS(Compare-And-Swap):比较并交换指令,用于实现无锁编程。
  • XCHG(Exchange):交换指令,用于原子性地交换两个寄存器的值。
  • LOCK前缀:在x86架构中,LOCK前缀可以与大多数的内存操作指令结合使用,确保该指令的原子性执行。它通过锁定总线来阻止其他处理器在执行期间访问被锁定的内存地址。
  1. mov eax, 0 ; 旧值(expected value
  2. mov ebx, 10 ; 新值(new value
  3. mov ecx, shared_data ; 共享数据的地址
  4. lock cmpxchg [ecx], ebx ; 执行CAS操作

LCB

LCB锁模型

LCB锁模型是一种基于锁控制块的锁实现方式,它通过维护锁的状态和持有者信息来管理资源的访问。以下是一个典型的LCB锁模型

  1. struct mutex {
  2. struct mutex_base base; // 基础锁结构
  3. struct task_struct *owner; // 锁的持有者
  4. struct mutex_waiter *waiters; // 等待task列表
  5. int saved_state; // 保存的状态
  6. int save_state; // 保存状态的标志
  7. int deadlock_detection; // 死锁检测标志
  8. int detect_deadlock; // 检测死锁的标志
  9. ......
  10. };

LCB锁-业务

公平锁

公平锁是一种锁策略,它保证线程按照它们请求锁的顺序来获取锁。这种策略对于避免线程饥饿和确保系统公平性至关重要。

设计

在LCB锁模型中,可以通过设计一个先进先出(FIFO)的等待队列来实现公平锁。等待队列存储所有请求锁但尚未获得锁的线程。当锁被释放时,队列中的第一个线程将获得锁。

  1. struct mutex_waiter {
  2. struct task_struct *task; // 等待锁的线程
  3. struct mutex_waiter *next; // 指向下一个等待者
  4. };
实现步骤
  1. 请求锁:当一个线程请求锁时,如果锁已经被其他线程持有,则该线程被添加到等待队列的末尾。
  2. 释放锁:当持有锁的线程释放锁时,从等待队列的头部取出一个线程,并将其设置为新的锁持有者。
  3. 死锁检测:如果等待队列中的线程长时间无法获得锁,可以通过死锁检测机制来识别并处理潜在的死锁问题。

最高优先级锁

最高优先级锁是一种锁策略,它根据线程的优先级来决定锁的授予。高优先级的线程会优先获得锁,从而确保关键任务能够及时执行。

设计

在LCB锁模型中,可以通过增加一个字段来存储线程的优先级,以便在锁的分配中使用

  1. struct mutex_waiter {
  2. struct task_struct *task; // 等待锁的线程
  3. int priority; // 线程的优先级
  4. struct mutex_waiter *next; // 指向下一个等待者
  5. };
实现步骤
  1. 设置优先级:每个线程在创建时或请求锁之前设置自己的优先级。
  2. 请求锁:线程将优先级作为请求锁的一部分,如果锁已被占用,则根据优先级将线程添加到等待队列的适当位置。
  3. 释放锁:当锁被释放时,从等待队列中选择优先级最高的线程作为新的锁持有者。

其他业务锁的设计

根据具体业务,配合进程/线程的调度策略,基于LCB锁的模型设计出来自己想要的锁。

总结

本文探讨了锁在并发编程中的作用,包括其概念、软件与硬件实现方式,以及LCB锁模型和不同业务锁策略。希望本文可以对你理解锁有帮助!