← 中文资源

简体中文版本

可重入锁

可重入锁允许持有线程再次获取同一把锁并递增持有计数——防止合法重入受保护区间的代码路径自死锁。

阅读英文原文

概念定义

线程调用加锁的方法 A,A 又调用同样加锁的方法 B:普通锁下线程永远等自己;可重入锁识别属主并成功,按深度计数直到配对的释放逐层解开。

工作机制

锁记录属主身份与递归计数:属主获取自增、解锁自减、归零才真正释放给他人。公平性、带超时的 try-lock、条件变量是 ReentrantLock 这类库实现的附加能力。

使用场景

受保护方法互相调用的公共 API、在锁定区调用用户覆写的框架、以及遗留调用形态的兼容。可选时应收窄锁范围,让重入变得不必要。

常见误区

可重入只防单线程自死锁,多线程环照死不误——加锁顺序纪律仍然必须。它还会把“意外的递归加锁”静默掉:代码看似正常,性能与不变量却在退化。循环里多次获取只释放一次会腐蚀计数;条件等待期间的锁释放与深度记账交互也常出意外。重入不是免性能的——每次获取释放都多付簿记。

代码示例

-- PostgreSQL 会话级咨询锁:try 锁拿不到立即返回假
SELECT pg_try_advisory_lock(4271);

-- 若返回 true,此会话独占编号 4271 的锁
-- 其他实例的尝试会得到 false:
SELECT pg_try_advisory_lock(4271);  -- false:已被占用

-- 临界区:只有一个实例在执行对账
BEGIN;
SELECT pg_advisory_xact_lock(4271);  -- 事务级:提交或回滚自动释放

UPDATE reconciliation_runs
  SET started_at = now(), status = 'running'
  WHERE id = 'nightly-2026-10-21';

-- ... 对账主体 ...

UPDATE reconciliation_runs
  SET finished_at = now(), status = 'done'
  WHERE id = 'nightly-2026-10-21';
COMMIT;  -- 锁随事务结束自动归还

-- 会话级锁的显式释放路径
SELECT pg_advisory_unlock(4271);

-- 双键形式:类目 ID 加实体 ID 组合编号
SELECT pg_try_advisory_lock(4271, 42);

-- 锁状态观测
SELECT pg_locks.locktype, pg_locks.objid, pg_locks.granted
FROM pg_locks
WHERE locktype = 'advisory';
#!/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' "$"