← 中文资源

简体中文版本

优先级反转

优先级反转让高优先级任务等待低优先级任务持有的锁,而后者又被中优先级任务抢占 CPU——火星探测器宕机就是此因。

阅读英文原文

概念定义

优先级调度假设“最重要的可运行任务在跑”。锁打破了这个假设:重要任务阻塞在资源上,资源持有者不重要且可运行却被抢占,无关的中优先级任务又让持有者跑不完——重要任务在等调度器眼里“配不上它”的工作。

工作机制

优先级继承把锁持有者临时提升到最高等待者优先级直到释放,中优先级便抢不走它。优先级天花板更进一步:获取任何已知天花板锁就把持有者抬到该天花板,防死锁也防链式反转。POSIX 互斥量暴露了这些协议属性。

使用场景

截止时间关键路径、音频回调、控制回路,以及与尽力而为工作共享状态的遥测线程。可行时优先在优先级之间用无锁交接或消息队列。

常见误区

“设了优先级就安全”错在锁不受调度器管——反转恰恰发生在调度与加锁之间;非实时系统只要有优先级调度加共享锁也会中招。继承给每次加锁加开销,须选择性启用。A 等 B、B 等 C 的无界反转链需要天花板协议或重设计。看门狗复位只会掩盖反转并让状态在操作中途损坏——探针事故正是这个样子。

代码示例

export type Release = () => void;

export class ReadWriteLock {
  private readers = 0;
  private writer = false;
  private waitingReaders: Array<() => void> = [];
  private waitingWriters: Array<() => void> = [];

  async acquireRead(): Promise<Release> {
    if (!this.writer && this.waitingWriters.length === 0) {
      this.readers += 1;
      return this.createReadRelease();
    }
    await new Promise<void>((resolve) => this.waitingReaders.push(resolve));
    return this.createReadRelease();
  }

  async acquireWrite(): Promise<Release> {
    if (!this.writer && this.readers === 0 && this.waitingWriters.length === 0) {
      this.writer = true;
      return this.createWriteRelease();
    }
    await new Promise<void>((resolve) => this.waitingWriters.push(resolve));
    return this.createWriteRelease();
  }

  private createReadRelease(): Release {
    let released = false;
    return () => {
      if (released) return;
      released = true;
      this.readers -= 1;
      if (this.readers === 0 && this.waitingWriters.length > 0) {
        this.writer = true;
        this.waitingWriters.shift()!();
      }
    };
  }

  private createWriteRelease(): Release {
    let released = false;
    return () => {
      if (released) return;
      released = true;
      this.writer = false;
      if (this.waitingWriters.length > 0) {
        this.writer = true;
        this.waitingWriters.shift()!();
        return;
      }
      const readers = this.waitingReaders.splice(0);
      this.readers += readers.length;
      for (const resolve of readers) resolve();
    };
  }

  get state(): {
    readers: number;
    writer: boolean;
    waitingReaders: number;
    waitingWriters: number;
  } {
    return {
      readers: this.readers,
      writer: this.writer,
      waitingReaders: this.waitingReaders.length,
      waitingWriters: this.waitingWriters.length,
    };
  }
}
#!/usr/bin/env bash
set -euo pipefail

lock_file=${1:-"${TMPDIR:-/tmp}/devexamples-backup.lock"}
max_wait=${LOCK_WAIT_SECONDS:-5}
work_file=""

cleanup() {
    local status=$?
    if [[ -n "$work_file" && -f "$work_file" ]]; then
        rm -f -- "$work_file"
    fi
    flock -u 9 2>/dev/null || true
    exit "$status"
}
trap cleanup EXIT

exec 9>"$lock_file"

if ! flock -w "$max_wait" 9; then
    printf 'could not acquire %s within %ss\n' "$lock_file" "$max_wait" >&2
    exit 75
fi

printf 'lock acquired by pid %d\n' "$"

work_file="$(mktemp "${TMPDIR:-/tmp}/backup.XXXXXX")"
printf 'job %d started at %s\n' "$" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$work_file"

sleep "${JOB_SECONDS:-1}"
cat -- "$work_file"
printf 'job %d finished; lock released on exit\n' "$"