← 中文资源

简体中文版本

ABA 问题

ABA 问题欺骗基于 CAS 的算法:值先变为别的再变回期望值,交换成功,但中间的变化已使算法假设失效。

阅读英文原文

概念定义

CAS 检查的是“值相等”,不是“没变过”。世界从 A 到 B 再回到 A 时,CAS 会当作世界静止不动。正确性取决于其间发生了什么:被释放又被重分配为不同逻辑元素的节点,让指针相等失去意义。

工作机制

经典栈弹出场景:线程读到栈顶 A;另一线程弹出 A、压入 B、再压回 A——同一节点。第一个线程的 CAS 成功并顺着 A 的 next 链接,而它可能已属于另一条逻辑链。对策是每次变更递增标签的版本化指针,以及推迟释放的 hazard pointer 与 epoch 回收。

使用场景

一切把 CAS 成功转成“内存结构未变”的结论的地方。托管运行时消灭了释放复用变体,但对象池、槽位与索引复用仍会造出逻辑 ABA。

常见误区

“GC 解决 ABA”只对了半个:GC 防释放后使用,防不了池化复用的逻辑 ABA。标签位有宽度上限,16 位标签在热结构里终会绕回零;hazard pointer 加的是每读者簿记成本,epoch 加的是回收延迟。优先用成熟库——手工修 ABA 的历史充满微妙失败。

代码示例

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,
    };
  }
}
interface WeightedNode<T> {
  value: T;
  weight: number;
  current: number;
}

export class SmoothWeightedRoundRobin<T> {
  private readonly nodes: WeightedNode<T>[] = [];
  private totalWeight = 0;

  add(value: T, weight: number): this {
    if (!Number.isInteger(weight) || weight < 1) {
      throw new RangeError("weight must be a positive integer");
    }
    this.nodes.push({ value, weight, current: 0 });
    this.totalWeight += weight;
    return this;
  }

  remove(value: T): boolean {
    const index = this.nodes.findIndex((node) => Object.is(node.value, value));
    if (index === -1) return false;
    this.totalWeight -= this.nodes[index].weight;
    this.nodes.splice(index, 1);
    return true;
  }

  next(): T {
    if (this.nodes.length === 0) throw new Error("no nodes available");
    let best = this.nodes[0];
    for (const node of this.nodes) {
      node.current += node.weight;
      if (node.current > best.current) best = node;
    }
    best.current -= this.totalWeight;
    return best.value;
  }

  get size(): number {
    return this.nodes.length;
  }
}