JavaScript 中使用锁的场景

在 JavaScript 中,由于单线程特性,通常不需要传统多线程环境中的锁机制。但在异步编程、Web Workers 和某些并发场景下,仍然需要类似的同步机制。以下是常见的使用场景:

1. 异步操作竞态条件

当多个异步操作可能同时修改共享状态时:

class AsyncLock {
  constructor() {
    this.locked = false;
    this.queue = [];
  }

  acquire() {
    return new Promise(resolve => {
      if (!this.locked) {
        this.locked = true;
        resolve();
      } else {
        this.queue.push(resolve);
      }
    });
  }

  release() {
    if (this.queue.length > 0) {
      const next = this.queue.shift();
      next();
    } else {
      this.locked = false;
    }
  }
}

// 使用示例
const lock = new AsyncLock();
let sharedResource = 0;

async function criticalSection() {
  await lock.acquire();
  try {
    // 对共享资源的操作
    sharedResource++;
    console.log('Modified shared resource:', sharedResource);
  } finally {
    lock.release();
  }
}

// 多个异步操作同时调用
criticalSection();
criticalSection();

2. Web Workers 间的数据同步

当多个 Web Workers 需要访问共享数据时:

// 主线程
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);

// 创建锁 (基于 Atomics)
const lock = new Int32Array(sharedBuffer, 0, 1);

// Worker 中使用
function workerFunction() {
  // 获取锁
  while (Atomics.compareExchange(lock, 0, 0, 1) !== 0) {
    // 等待锁释放
    Atomics.wait(lock, 0, 1);
  }
  
  try {
    // 临界区代码
    console.log('Worker acquired lock');
  } finally {
    // 释放锁
    Atomics.store(lock, 0, 0);
    Atomics.notify(lock, 0, 1);
  }
}

3. 防止重复请求/操作

在用户界面中防止重复点击或提交:

function createButtonLock() {
  let isLocked = false;
  
  return async function lockedAction(action) {
    if (isLocked) {
      console.log('Action is already in progress');
      return;
    }
    
    isLocked = true;
    try {
      await action();
    } finally {
      isLocked = false;
    }
  };
}

// 使用
const submitForm = createButtonLock();

button.addEventListener('click', () => {
  submitForm(async () => {
    // 表单提交逻辑
    await fetch('/api/submit', { method: 'POST' });
  });
});

4. 数据库操作序列化

确保数据库操作按顺序执行:

class DatabaseLock {
  constructor() {
    this.pendingOperations = new Map();
  }

  async lockFor(key, operation) {
    if (this.pendingOperations.has(key)) {
      // 等待现有操作完成
      await this.pendingOperations.get(key);
    }
    
    const operationPromise = operation();
    this.pendingOperations.set(key, operationPromise);
    
    try {
      return await operationPromise;
    } finally {
      this.pendingOperations.delete(key);
    }
  }
}

// 使用
const dbLock = new DatabaseLock();

async function updateUserProfile(userId, updates) {
  return dbLock.lockFor(`user-${userId}`, async () => {
    // 数据库操作
    const user = await db.getUser(userId);
    Object.assign(user, updates);
    await db.saveUser(user);
    return user;
  });
}

5. 缓存更新同步

防止缓存击穿和重复更新:

class CacheWithLock {
  constructor() {
    this.cache = new Map();
    this.locks = new Map();
  }

  async get(key, factory) {
    if (this.cache.has(key)) {
      return this.cache.get(key);
    }
    
    if (!this.locks.has(key)) {
      this.locks.set(key, Promise.resolve().then(async () => {
        const value = await factory();
        this.cache.set(key, value);
        this.locks.delete(key);
        return value;
      }));
    }
    
    return this.locks.get(key);
  }
}

// 使用
const cache = new CacheWithLock();

async function getExpensiveData(key) {
  return cache.get(key, async () => {
    // 昂贵的计算或网络请求
    const data = await fetchDataFromServer(key);
    return data;
  });
}

注意事项

  1. 避免死锁:确保锁总是被释放,使用 try-finally 结构
  2. 性能考虑:锁会降低并发性,只在必要时使用
  3. 超时机制:为锁操作添加超时,防止永久阻塞
  4. 浏览器兼容性:SharedArrayBuffer 和 Atomics API 在某些环境下可能受限

JavaScript 中的锁机制主要用于协调异步操作,而不是传统意义上的线程同步。在设计时应充分考虑 JavaScript 的事件循环特性。

更多推荐