mutex 锁机制简介

mutex 锁机制简介

互斥体的语义

  • 一次只有一个任务可以持有互斥锁
  • 只有所有者才能解锁互斥体
  • 不允许多次解锁
  • 不允许递归锁定
  • 互斥对象必须通过 API 初始化
  • 互斥对象不得通过 memset 或复制来初始化
  • 任务可能无法在持有互斥体的情况下退出
  • 不得释放持有锁的内存区域
  • 持有的互斥体不得重新初始化
  • 互斥体不能在硬件或软件中断上下文中使用,例如微线程和定时器

核心数据结构

struct mutex {
    /*
     * 当前拥有互斥锁的线程标识。以原子方式存储和访问线程标识。
     * owner 为持有所的任务的地址 | 标志位
     * 原子操作要去地址是64位对齐的,所以低三位的地址固定为 0,这里借用低三位来做为锁状态的标志位
     */
    atomic_long_t       owner;
    
    // 原子自旋锁,用于保护互斥锁的等待队列(wait_list)和其他相关字段的访问。
    raw_spinlock_t      wait_lock;
    /*
     * midill path
     * 乐观自旋队列(optimistic spin queue),用于在互斥锁的拥有者上自旋等待。
     */
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
    struct optimistic_spin_queue osq; 
#endif
    
    //该锁的等待队列,在慢速路径上等待该锁的所有任务都挂载此处
    struct list_head    wait_list;  
    
    //用于调试目的的特殊字段
#ifdef CONFIG_DEBUG_MUTEXES
    void            *magic; 
#endif
    
    //一个锁依赖映射(lockdep_map),用于跟踪锁的依赖关系。
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map  dep_map; 
#endif
};

锁的状态

  • MUTEX_FLAG_WAITERS

    该互斥锁至少有一个等待被唤醒的任务在等待队列上

  • MUTEX_FLAG_HANDOFF

    该互斥锁正在从一个任务转移到另一个任务

  • MUTEX_FLAG_PICKUP

    该互斥锁已经完成了转移,即该互斥锁正在被一个新任务接管

锁的原理

在锁的结构体中有一个重要的成员变量 owner,该成员变量保存了锁的持有者的地址和锁的状态信息,而我们在判断一个锁是否被持有的标准也是查看此变量是否为 0 —— 因为一个变量或函数的地址为 0 表示该地址无效。

如果一个互斥锁的 owner不为 0 表示被该锁已经被持有,这时我们将申请该锁的任务加入到锁的等待队列 wait_list上。等到锁被释放时,就从等待队列上摘取最早等待的任务,并唤醒该任务去获取锁。

因为互斥锁只能同时被一个任务所持有,所以每次只唤醒一个任务,而没有必要唤醒所有的任务从而产生惊群现象。

锁的三种路径

  • 快速路径

    尝试通过使用当前任务的 atomic_long_try_cmpxchg_acquire()函数来原子地获取锁 。
    这只适用于无竞争的情况(cmpxchg() 检查 0UL,因此上面的所有 3 个状态位都必须为 0)。
    如果锁被争用,它将转到下一个可能的路径。

  • 中速路径

    中速路经又名乐观旋转,在锁所有者正在运行并且没有其他具有更高优先级(need_resched)的任务准备运行时
    尝试旋转以获取。 因为如果锁拥有者正在运行,很可能很快就会释放锁。互斥量旋转器使用 MCS 锁进行排队,以便只有一个旋转器可以竞争互斥量。

  • 慢速路径

    这是最后的手段,如果等待者仍然无法获取锁,则将任务添加到等待队列中并休眠,直到被解锁路径唤醒。
    正常情况下,它会以 TASK_UNINTERRUPTIBLE 状态阻塞。

MCS 锁

  • 介绍

    MCS(Mellor-Crummey and Scott)锁是一种用于实现互斥访问的自旋锁算法。它提供了一种公平的互斥机制,允许等待线程按照先到先服务的顺序获取锁,避免了饥饿问题。

  • MCS 锁的基本思想

    将等待线程组织成一个链表,每个线程都持有一个自己的节点(MCS 节点)。这个链表的头节点是当前拥有锁的线程,而每个节点都包含一个指向下一个节点的指针。当一个线程想要获取 MCS 锁时,它需要在链表中申请一个节点,并将节点的指针设置为 NULL。然后,线程在前一个节点上自旋等待,直到它的前一个节点将其指针设置为该节点。这样,线程就可以安全地进入临界区。

    当线程完成临界区的操作后,它会检查自己的节点的指针是否为 NULL,如果不为 NULL,说明后续有等待的线程,线程需要释放自己的节点,并通知下一个等待的线程可以获取锁。

  • 优缺点

    MCS 锁的主要优点是提供了公平性,保证了等待线程按照先到先服务的顺序获取锁,避免了饥饿问题。
    然而,与其他自旋锁算法相比,MCS 锁的实现相对较复杂,需要维护链表和节点之间的指针关系。

在实际使用中,MCS 锁通常用于多处理器系统或多核心系统中,以提供对共享资源的互斥访问。它在一些操作系统和并发编程框架中得到广泛应用。

锁的核心函数

  • 加锁

快速路径函数

static __always_inline bool __mutex_trylock_fast(struct mutex *lock)
{                                                                   
    unsigned long curr = (unsigned long)current;                    
    unsigned long zero = 0UL;                                       
                                                                    
    // 只执行一次原子交换指令,成功返回 true 失败返回 false   
    if (atomic_long_try_cmpxchg_acquire(&lock->owner, &zero, curr)) 
        return true;                                                
                                                                    
    return false;                                                   
}                                                                   

中速路径函数

static __always_inline bool 年mutex_optimistic_spin(struct mutex *lock, struct ww_acquire_ctx *ww_ctx,
              struct mutex_waiter *waiter)
{
    if (!waiter) {
        /*
         * mutex_can_spin_on_owner() 函数的目的是消除 osq_lock() 和 osq_unlock() 的开销,
         * 以防无法进行旋转。 由于 waiter-spinner 无论如何都不会获取 OSQ 锁,
         * 因此无需调用 mutex_can_spin_on_owner()。
         */
        if (!mutex_can_spin_on_owner(lock))
            goto fail;

        /*
         * 为了避免互斥旋转器试图一次性获取互斥锁的蜂拥而至,
         * 旋转器需要先获取 MCS(排队)锁,然后再在所有者字段上旋转。
         */
        if (!osq_lock(&lock->osq))
            goto fail;
    }

    // 自旋,直到没有 owner 或者 系统不允许自旋
    for (;;) {
        struct task_struct *owner;

        /* Try to acquire the mutex... */
        owner = __mutex_trylock_or_owner(lock);
        if (!owner)
            break;

        // 有一个 owner,等待它释放锁或进入睡眠状态。
        if (!mutex_spin_on_owner(lock, owner, ww_ctx, waiter))
            goto fail_unlock;

        cpu_relax();
    }

    if (!waiter)
        osq_unlock(&lock->osq);

    return true;


fail_unlock:
    if (!waiter)
        osq_unlock(&lock->osq);

fail:
    /*
     * 如果我们因为 need_resched() 而脱离了旋转路径,请在尝试锁定互斥锁之前立即重新安排。
     * 这可以避免在我们获得互斥体后立即被调度出去。
     */
    if (need_resched()) {
        __set_current_state(TASK_RUNNING);
        schedule_preempt_disabled();
    }

    return false;
}

慢速路径函数

static __always_inline int __sched                                             
__mutex_lock_common(struct mutex *lock, unsigned int state, unsigned int subclass,                                               
            struct lockdep_map *nest_lock, unsigned long ip,                   
            struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)              
{                                                                              
    struct mutex_waiter waiter;                                                
    struct ww_mutex *ww;                                                       
    int ret;                                                                   
                                                                               
    if (!use_ww_ctx)                                                           
        ww_ctx = NULL;                                                         
                                                                               
    // 调度抢占点                                                        
    might_sleep();                                                             
                                                                               
    MUTEX_WARN_ON(lock->magic != lock);                                        
                                                                               
    ww = container_of(lock, struct ww_mutex, base);                            
    // 在 ww_ctx 存在的情况下,如果两个地址相同,则说明是已经加锁的情况  
    if (ww_ctx) {                                                              
        if (unlikely(ww_ctx == READ_ONCE(ww->ctx)))                            
            return -EALREADY;                                                  
                                                                               
        /*                                                                    
         * 被 kill (接收到信号) 后重置受伤标志。                               
         * 没有其他进程可以在这里竞争并伤害我们,                              
         * 因为如果我们没有持有任何锁,它们就无法拥有有效的所有者指针。        
         */                                                                    
        if (ww_ctx->acquired == 0)                                             
            ww_ctx->wounded = 0;                                               
                                                                               
#ifdef CONFIG_DEBUG_LOCK_ALLOC                                                 
        nest_lock = &ww_ctx->dep_map;                                          
#endif                                                                         
    }                                                                          
                                                                               
    preempt_disable();                                                         
    // 锁递归检查                                                        
    mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);            
                                                                               
    // 尝试上锁成功(通过快速路径 || 乐观路径)                            
    if (__mutex_trylock(lock) ||                                               
        mutex_optimistic_spin(lock, ww_ctx, NULL)) {                                                                        
        lock_acquired(&lock->dep_map, ip);                                     
        if (ww_ctx)                                                            
            ww_mutex_set_context_fastpath(ww, ww_ctx);                         
        preempt_enable();                                                      
        return 0;                                                              
    }                                                                          
                                                                               
    // 要操作等待队列了,所以要先加锁 wait_lock 保护,再操作等待队列     
    raw_spin_lock(&lock->wait_lock);                                           
                                                                       
    // 在将任务加入等待对列前再次检查是否可以获取锁,如果获取到锁直接跳出
    if (__mutex_trylock(lock)) {                                               
        if (ww_ctx)                                                            
            __ww_mutex_check_waiters(lock, ww_ctx);                            
                                                                               
        goto skip_wait;                                                        
    }                                                                          
                                                                               
    debug_mutex_lock_common(lock, &waiter);                                    
    // 等待节点的任务为当前任务                                          
    waiter.task = current;                                                     
    if (use_ww_ctx)                                                            
        waiter.ww_ctx = ww_ctx;                                                
                                                                               
    lock_contended(&lock->dep_map, ip);                                        
                                                                               
    if (!use_ww_ctx) {                                                                   
        // 将任务按照 FIFO 的方式加入到锁的等待队列中                    
        __mutex_add_waiter(lock, &waiter, &lock->wait_list);                   
    } else {                                                                   
 		// 按照中速路径将任务加入到等待队列中                                                                   
        ret = __ww_mutex_add_waiter(&waiter, lock, ww_ctx);                    
        if (ret)                                                               
            goto err_early_kill;                                               
    }                                                                          
                                                                               
    // 设置当前任务的状态                                                
    set_current_state(state);                                                  
    for (;;) {                                                                 
        bool first;                                                            
                                                                               
		// 再次获取锁                                                                
        if (__mutex_trylock(lock))                                             
            goto acquired;                                                     
                                                                 
        // 检查等前任务的信号,如果有挂起的信号,则退出                   
        if (signal_pending_state(state, current)) {                            
            ret = -EINTR;                                                      
            goto err;                                                          
        }                                                                      
                                                                               
        if (ww_ctx) {                                                          
            ret = __ww_mutex_check_kill(lock, &waiter, ww_ctx);                
            if (ret)                                                           
                goto err;                                                      
        }                                                                      
                                                                               
        // 解锁等待队列的锁                                              
        raw_spin_unlock(&lock->wait_lock);                                     
                                                                               
        // 该函数为开启抢占,调度,返回后恢复禁止抢占                    
        schedule_preempt_disabled();                                           
                                                                               
        // 检查是否是第一个等待者。                                      
        first = __mutex_waiter_is_first(lock, &waiter);                        
                                                                               
        set_current_state(state);                                              
                                                               
        // 锁移交成功                                                    
        if (__mutex_trylock_or_handoff(lock, first) ||                         
            (first && mutex_optimistic_spin(lock, ww_ctx, &waiter)))           
            break;                                                             
                                                                               
        // 获取锁失败,则重新操作——为了和前面保持一直,这里需要加锁循环  
        raw_spin_lock(&lock->wait_lock);                                       
    }                                                                          
    // 获取锁成功后,需要操作等待队列,所以加锁                          
    raw_spin_lock(&lock->wait_lock);                                           
acquired:                                                                      
    // 设置当前任务状态为运行态                                          
    __set_current_state(TASK_RUNNING);                                         
                                                                               
    if (ww_ctx) {                                                                  
        if (!ww_ctx->is_wait_die &&                                            
            !__mutex_waiter_is_first(lock, &waiter))                           
            __ww_mutex_check_waiters(lock, ww_ctx);                            
    }                                                                          
                                                                               
    // 从等待队列上删除本节点                                            
    __mutex_remove_waiter(lock, &waiter);                                      
                                                                               
    debug_mutex_free_waiter(&waiter);                                          
                                                                               
skip_wait:                                                                                                      
    lock_acquired(&lock->dep_map, ip);                                         
                                                                               
    if (ww_ctx)                                                                
        ww_mutex_lock_acquired(ww, ww_ctx);                                    
                                                                               
    // 释放锁,并允许抢占(其中回运行一次抢占调度)                        
    raw_spin_unlock(&lock->wait_lock);                                         
    preempt_enable();                                                          
    return 0;                                                                  
                                                                               
err:                                                                           
    // 如果出错,恢复任务正常的运行态,并从等待队列中删除自己            
    __set_current_state(TASK_RUNNING);                                         
    __mutex_remove_waiter(lock, &waiter);                                      
err_early_kill:                                                                
    raw_spin_unlock(&lock->wait_lock);                                         
    debug_mutex_free_waiter(&waiter);                                          
    mutex_release(&lock->dep_map, ip);                                         
    preempt_enable();                                                          
    return ret;                                                                
}                                                                              
  • 解锁
static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigned long ip)
{
    struct task_struct *next = NULL;
    DEFINE_WAKE_Q(wake_q);
    unsigned long owner;

    // 调用 lockdep 中的 release 函数,用户释放锁检查
    mutex_release(&lock->dep_map, ip);

    /*
     * 在(可能)获取自旋锁之前释放锁,以便其他竞争者可以尽快继续处理事情。
     *
     * 除非 HANDOFF 时,在这种情况下我们不能清除所有者字段,而是将其设置为顶级等待者。
     */
    owner = atomic_long_read(&lock->owner);
    for (;;) {
        MUTEX_WARN_ON(__owner_task(owner) != current);
        MUTEX_WARN_ON(owner & MUTEX_FLAG_PICKUP);

        // 锁正在交接
        if (owner & MUTEX_FLAG_HANDOFF)
            break;
	
        // 清除锁的状态;如果老状态为存在等待者,则跳出循环,否则退出
        if (atomic_long_try_cmpxchg_release(&lock->owner, &owner, __owner_flags(owner))) {
            if (owner & MUTEX_FLAG_WAITERS)
                break;

            return;
        }
    }

    // 要操作等待队列,所以使用 wait_lock 进行保护
    raw_spin_lock(&lock->wait_lock);
    debug_mutex_unlock(lock);
    // 在等待列表不为空时,从等待列表中选取第一个任务,并将其放入到唤醒等待队列中                                                             
    if (!list_empty(&lock->wait_list)) {
        struct mutex_waiter *waiter =
            list_first_entry(&lock->wait_list,
                     struct mutex_waiter, list);

        next = waiter->task;

        debug_mutex_wake_waiter(lock, waiter);
        wake_q_add(&wake_q, next);
    }

    // 如果锁允许交接,则直接设置该任务为锁的持有者
    if (owner & MUTEX_FLAG_HANDOFF)
        __mutex_handoff(lock, next);

    raw_spin_unlock(&lock->wait_lock);
	// 唤醒等待队列上的任务
    wake_up_q(&wake_q);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
vector是C++标准库中的一个容器,用于存储和管理一组元素。在多线程环境下,如果多个线程同时对vector进行读写操作,就会存在数据竞争的问题,为了保证线程安全,可以采用锁机制来对vector进行保护。 一种常见的锁机制是使用互斥锁(mutex)。互斥锁是一种同步原语,它可以确保在同一时间只有一个线程可以访问被保护的资源。在对vector进行读写操作之前,线程需要先获取互斥锁,然后执行相应的操作,最后释放互斥锁,以便其他线程可以获取锁并进行操作。 下面是一个简单的示例代码,演示了如何使用互斥锁来保护vector: ```cpp #include <iostream> #include <vector> #include <mutex> #include <thread> std::vector<int> myVector; std::mutex mtx; void addToVector(int value) { std::lock_guard<std::mutex> lock(mtx); // 获取互斥锁 myVector.push_back(value); // 对vector进行写操作 } void printVector() { std::lock_guard<std::mutex> lock(mtx); // 获取互斥锁 for (const auto& value : myVector) { // 对vector进行读操作 std::cout << value << " "; } std::cout << std::endl; } int main() { std::thread t1(addToVector, 1); std::thread t2(addToVector, 2); std::thread t3(printVector); t1.join(); t2.join(); t3.join(); return 0; } ``` 在上述代码中,使用了std::mutex来定义互斥锁mtx。在addToVector函数中,通过std::lock_guard<std::mutex> lock(mtx)获取互斥锁,确保只有一个线程可以执行push_back操作。在printVector函数中,同样使用std::lock_guard<std::mutex> lock(mtx)获取互斥锁,确保只有一个线程可以遍历vector并输出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值