#linux #thread

pcp-mutex

基于 Linux PI futex 的优先级上限协议(PCP)互斥锁。允许高效且无死锁的执行。

4 个版本

0.2.0 2021 年 10 月 12 日
0.1.2 2021 年 10 月 6 日
0.1.1 2021 年 10 月 6 日
0.1.0 2021 年 10 月 6 日

#482 in 并发

每月 22 次下载
用于 linux-rtic

Apache-2.0 或 MIT

19KB
261 行(不含注释)

pcp-mutex

License Cargo Documentation

基于 Linux PI futex 的优先级上限协议(PCP)互斥锁。允许高效且无死锁的执行。

工作原理

Linux 内核不支持优先级上限协议(PCP),这对于实时系统来说至关重要,既能保证良好的可调度性,又能确保无死锁执行。该包使用优先级继承(PI)futex 模拟原始优先级上限协议(OPCP)。

在内部,每个 PcpMutex 都属于某个 PcpGroup,该组包含对最高锁持有者的原子指针。任何尝试锁定 PcpMutex 的操作首先会与这个最高锁持有者进行比较,如果满足 PCP 锁定条件,则会成功。futex 通过涉及零系统调用的原子操作进行锁定。如果失败,当前线程将在最高锁持有者上调用 futex LOCK_PI 系统调用,内核将提高锁持有者的优先级。

默认情况下,PcpMutex::new() 使用默认的全局组。程序的不同部分可以使用多个组进行隔离。

这在与 POSIX PTHREAD_PRIO_PROTECT 互斥锁的两个方面不同

  • POSIX 互斥锁在每次锁定/解锁时都会调用 sched_setparam 系统调用,这要慢得多。该库仅在快速路径上使用原子操作。
  • POSIX 互斥锁是独立的(没有系统上限)并且不能防止死锁。它不是一个“真正的”PCP。

每个线程还持有内部线程局部 ThreadState 对象,该对象包含线程 id(来自 gettid 系统调用)和优先级(来自 sched_getparam),用于内部逻辑。如果线程优先级被手动更改(即 sched_setparam 系统调用),则必须调用 thread::update_priority() 来更新内部状态。对于实时应用程序,有一个便利函数 thread::init_fifo_priority(priority: u8),它将调度策略设置为 SCHED_FIFO 并给定优先级,同时更新内部状态。在持有互斥锁时避免更改线程优先级,因为这可能导致死锁。

示例

以不同的顺序锁定 2 个互斥锁会导致死锁,但 PCP 可以防止这种情况

let a = Arc::new(PcpMutex::new(0, 3));
let b = Arc::new(PcpMutex::new(0, 3));

{
    let a = a.clone();
    let b = b.clone();
    thread::spawn(move || {
        println!("Thread 1 tries a lock");
        a.lock(|a| {
            println!("Thread 1 holds a lock");
            *a += 1;
            thread::sleep(std::time::Duration::from_millis(100));
            println!("Thread 1 tries b lock");
            b.lock(|b| {
                println!("Thread 1 holds b lock");
                *b += 1;
            });
            println!("Thread 1 released b lock");
        });
        println!("Thread 1 released a lock");
    });
}

{
    let a = a.clone();
    let b = b.clone();
    thread::spawn(move || {
        println!("Thread 2 tries b lock");
        b.lock(|b| {
            println!("Thread 2 holds b lock");
            *b += 1;
            thread::sleep(std::time::Duration::from_millis(100));
            println!("Thread 2 tries a lock");
            a.lock(|a| {
                println!("Thread 2 holds a lock");
                *a += 1;
            });
            println!("Thread 2 released a lock");
        });
        println!("Thread 2 released b lock");
    });
}

输出

Thread 1 tries a lock
Thread 1 holds a lock
Thread 2 tries b lock <--- thread 2 is prevented from taking a lock here by PCP
Thread 1 tries b lock
Thread 1 holds b lock
Thread 1 released b lock
Thread 1 released a lock
Thread 2 holds b lock
Thread 2 tries a lock
Thread 2 holds a lock
Thread 2 released a lock
Thread 2 released b lock

鸣谢

这项工作是我于屯特大学论文的一部分。

依赖关系

~80KB