内核无锁第四层级 — 免锁

本文介绍环形缓冲区(Ring Buffer)在单一生产者和单一消费者模型下的无锁访问机制,阐述了如何通过精心设计的数据结构确保数据的一致性,并提供了具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

环形缓冲区是生产者和消费者模型中常用的数据结构。生产者将数据放入数组的尾端,而消费者从数组的另一端移走数据,当达到数组的尾部时,生产者绕回到数组的头部。

如果只有一个生产者和一个消费者,那么就可以做到免锁访问环形缓冲区(Ring Buffer)。写入索引只允许生产者访问并修改,只要写入者在更新索引之前将新的值保存到缓冲区中,则读者将始终看到一致的数据结构。同理,读取索引也只允许消费者访问并修改。

图 2. 环形缓冲区实现原理图 

如图所示,当读者和写者指针相等时,表明缓冲区是空的,而只要写入指针比读取指针多fifo->size时,表明缓冲区已满。

kfifo的定义文件:kernel/kfifo.c

kfifo的头文件:  include/linux/kfifo.h

struct kfifo { 
    unsigned char *buffer;    /* the buffer holding the data */ 
    unsigned int size;    /* the size of the allocated buffer */ 
    unsigned int in;    /* data is added at offset (in % size) */ 
    unsigned int out;    /* data is extracted from off. (out % size) */ 
    spinlock_t *lock;    /* protects concurrent modifications */ 
};

 

/* 
 * __kfifo_put - puts some data into the FIFO, no locking version 
 * Note that with only one concurrent reader and one concurrent 
 * writer, you don't need extra locking to use these functions. 
 */ 
 unsigned int __kfifo_put(struct kfifo *fifo, 
       unsigned char *buffer, unsigned int len) 
 { 
  unsigned int l; 
  len = min(len, fifo->size - fifo->in + fifo->out); 
  /* first put the data starting from fifo->in to buffer end */ 
  l = min(len, fifo->size - (fifo->in & (fifo->size - 1))); 
  memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l); 
  /* then put the rest (if any) at the beginning of the buffer */ 
  memcpy(fifo->buffer, buffer + l, len - l); 
  fifo->in += len; 
  return len; 
 } 

 /* 
 * __kfifo_get - gets some data from the FIFO, no locking version 
 * Note that with only one concurrent reader and one concurrent 
 * writer, you don't need extra locking to use these functions. 
 */ 
 unsigned int __kfifo_get(struct kfifo *fifo, 
     unsigned char *buffer, unsigned int len) 
 { 
  unsigned int l; 
  len = min(len, fifo->in - fifo->out); 
  /* first get the data from fifo->out until the end of the buffer */ 
  l = min(len, fifo->size - (fifo->out & (fifo->size - 1))); 
  memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l); 
  /* then get the rest (if any) from the beginning of the buffer */ 
  memcpy(buffer + l, fifo->buffer, len - l); 
  fifo->out += len; 
  return len; 
 }

注意第12行,仅当fifo->size等于2^n时,fifo->in & (fifo->size - 1)等于fifo->in%fifo->size,否则出错。

以上代码摘自 2.6.10 内核,通过代码的注释(斜体部分)可以看出,当只有一个消费者和一个生产者时,可以不用添加任何额外的锁,就能达到对共享数据的访问。


<think>嗯,用户让我讲解轻量级和重量级以及它们的原理。首先,我得回忆一下Java中的机制。我记得在Java的synchronized关键字背后,JVM会根据竞争情况自动升级的状态,从偏向到轻量级,再到重量级。不过用户可能不需要太详细的所有阶段,但重点在轻量级和重量级的区别和原理。 首先,轻量级的应用场景应该是没有多线程竞争的情况下。这时候,JVM为了避操作系统层面的互斥操作,使用轻量级来减少性能开销。原理方面,可能涉及对象头的Mark Word和线程栈中的记录。当线程进入同步块时,如果对象是无状态,JVM会在当前线程的栈帧中创建记录,然后通过CAS操作将对象头指向这个记录。如果成功,就获取了;如果失败,说明有其他线程竞争,可能升级为重量级。 重量级则是在有激烈竞争时使用的,这时候JVM会依赖操作系统的互斥量(mutex)和条件变量来实现,这会导致线程进入阻塞状态,需要进行用户态到内核态的切换,开销较大。重量级的原理可能涉及到Monitor对象,每个Java对象都关联一个Monitor,当线程获取Monitor时,其他线程会被阻塞,放入等待队列。 但是,我需要确认这些信息是否准确。比如,轻量级的升级过程,当CAS失败时,是否立即升级?或者是否有自旋的过程?可能我之前学的有些过时了,因为不同版本的JVM可能有不同的优化策略。比如在JDK 6之后,自旋是自适应的,或者有消除、粗化等优化,但用户的问题可能更关注基础原理。 另外,用户可能是为了面试准备或者学习多线程知识,需要明确两者的优缺点和适用场景。应该强调轻量级了操作系统层面的阻塞,而重量级则处理高竞争情况,但带来更大开销。 可能还需要提到膨胀的过程,即当轻量级竞争加剧时,会升级为重量级。这时候对象头的Mark Word会指向Monitor对象,而线程未获取到时会被阻塞,进入EntryList等待。 需要结构清晰,分点讲解,可能先讲轻量级,再讲重量级,最后对比两者的区别和原理。要注意使用中文,避专业术语错误,同时确保LaTeX格式的正确使用,比如对象头的结构可能用Mark Word($Mark\ Word$)表示,用$$包裹独立公式,行内变量用$符号。 可能用户还想知道为什么需要这两种,或者它们如何提升性能。比如轻量级减少内核态切换,而重量级虽然开销大,但能有效处理高并发竞争。这时候需要结合应用场景说明。 最后检查是否有遗漏点,比如的升级过程、CAS操作的具体步骤、以及Monitor的结构。确保回答准确,并且语言通俗易懂,适合不同层次的学习者。</think>以下是轻量级和重量级的原理对比及工作机制详解: --- ### 一、轻量级(Lightweight Lock) #### 适用场景 - 多线程**交替执行**同步代码块,无实际竞争 - 通过**避阻塞**减少用户态/内核态切换开销 #### 核心原理 1. **对象头标记** - 每个对象头部存储$Mark\ Word$(标记字段) - 无状态时$Mark\ Word$记录对象的哈希码、分代年龄等信息 2. **加过程** ```java synchronized(obj) { // 同步代码块 } ``` - 线程栈创建**记录空间(Lock Record)** - 通过CAS操作将$Mark\ Word$复制到记录,并尝试将对象头指向记录地址 - 成功:获得,对象头低2位变为`00`(轻量级标志) - 失败:检测是否发生重入(通过栈帧中的记录验证) 3. **解过程** - 使用原子操作将$Mark\ Word$恢复回对象头 - 若发现对象头已被修改,说明存在竞争,触发膨胀 --- ### 二、重量级(Heavyweight Lock) #### 适用场景 - 多线程**激烈竞争**同一资源 - 需要依赖操作系统实现线程调度 #### 核心原理 1. **Monitor对象** - 每个对象关联一个Monitor(管程) - 包含三个关键区域: - **Owner**:持有的线程 - **EntryList**:竞争的线程队列 - **WaitSet**:调用`wait()`的等待线程队列 2. **加过程** - 通过**mutex互斥量**实现(操作系统内核级指令) - 未获取的线程进入阻塞状态,被移入EntryList - 涉及用户态到内核态的切换,开销较大(约1μs级别) 3. **膨胀触发** - 当轻量级CAS更新失败时 - JVM将$Mark\ Word$指向Monitor对象 - 对象头标志位变为`10`(重量级标志) --- ### 三、性能对比 | 维度 | 轻量级 | 重量级 | |---------------|----------------------------|----------------------------| | 实现层级 | JVM层面 | 操作系统层面 | | 线程阻塞 | 自旋尝试(CAS) | 内核态阻塞 | | 内存开销 | 仅需记录空间 | 需维护Monitor对象及队列 | | 适用场景 | 低竞争、短时同步 | 高竞争、长时同步 | | 典型耗时 | 纳秒级(≈50ns) | 微秒级(≈1μs) | --- ### 四、优化策略 1. **自适应自旋(JDK6+)** - 根据前一次自旋时间动态调整自旋次数 2. **消除(Lock Elision)** - 通过逃逸分析消除不可能共享的 3. **粗化(Lock Coarsening)** - 合并连续多个细粒度为单个大范围 --- ### 示例代码分析 ```java public class LockExample { private final Object lock = new Object(); public void demo() { synchronized(lock) { // 可能经历升级过程 // 临界区代码 } } } ``` 1. 首次进入:偏向(如果开启) 2. 第二个线程进入:升级为轻量级 3. 持续竞争:最终升级为重量级 通过`jol-core`工具可查看对象头变化: ```java // 输出:org.openjdk.jol.vm.VM.current().details() Object header: 0x0000000000000001 (non-biasable) // 初始状态 → 0x0000700001b63d20 (thin lock) // 轻量级 → 0x0000700001b63d70 (heavy lock) // 重量级 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值