JS实现Rust中的Mutex机制

262 阅读2分钟

Mutex是什么,有什么作用

  • 在编程中,特别是并发编程中,Mutex(Mutual Exclusion,互斥锁)是一种用于防止多个线程同时访问共享资源的机制。它确保在任意时刻,最多只有一个线程可以持有锁,从而保护临界区内的数据免受数据竞争的影响。

  • 主要功能和用途

  1. 线程同步Mutex 用于在线程之间同步访问共享资源。例如,在多线程程序中,如果多个线程同时读取和写入共享数据,可能会导致数据不一致或损坏。Mutex 确保每次只有一个线程能够访问共享资源,从而防止数据竞争。
  2. 保护临界区: 临界区是指程序中访问共享资源的代码部分。Mutex 可以保护这些临界区,确保在一个线程完成对共享资源的访问之前,其他线程无法进入该临界区。
  3. 提高数据安全性: 通过使用 Mutex,可以确保共享资源在多线程环境中保持一致性和完整性,从而提高数据安全性。
  • 在Rust中简单使用Mutex

    use std::sync::{Arc, Mutex, MutexGuard};
    use std::thread;
    
    fn main() {
        let counter = Arc::new(Mutex::new(0));
        let mut handles = vec![];
    
        for _ in 0..10 {
            let counter = Arc::clone(&counter);
            let handle = thread::spawn(move || {
                let mut num: MutexGuard<i32> = counter.lock().unwrap();
                *num += 1;
            });// num会被自动drop释放掉资源
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    
        println!("Result: {}", *counter.lock().unwrap());
    }
    

JS中的数据竞争

  • JS虽然是一门单线程语言,但是在异步场景中可能也会产生数据竞争
  let sharedVariable = 0;

  async function incrementVariable () {
    const oldValue = sharedVariable;
    await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000)); // 模拟异步操作
    sharedVariable = oldValue + 1;
    console.log(`Incremented shared variable to ${sharedVariable}`);
  }


  async function main () {
    const tasks: Promise<void>[] = [];
    for (let i = 0; i < 5; i++) {
      tasks.push(incrementVariable());
    }
    await Promise.all(tasks);
    console.log(`Final shared variable value: ${sharedVariable}`);
  }

main();

结果如下:

$ bun index.ts 
Incremented shared variable to 1
Incremented shared variable to 1
Incremented shared variable to 1
Incremented shared variable to 1
Incremented shared variable to 1
Final shared variable value: 1

其实如果每个task依赖顺序,正常的解决方案来说是不应该使用Promise.all来处理的,直接一个一个遍历即可

 for ( let i = 0; i < 5; i++ ) {
    await incrementVariable();
  }

另外一种方法就是实现一个简单的Mutex

GPT提示是在竞争激烈时, 理论上锁会更快,for循环会慢一些, 但是简单实测了一下,依次迭代的速度是快于下面使用的Mutex的,怀疑是锁的开销比较大导致的

使用JS实现一个简单的Mutex

  • 实现Mutex
class Mutex<T> {
    private locked: boolean = false
    private waitingQueue: Array<() => void> = []
    value: T

    constructor(value: T) {
      this.locked = false;
      this.waitingQueue = [];
      this.value = value;
    }

    async lock (): Promise<MutexGuard<T>> {
      while (this.locked) {
        await new Promise<void>((resolve) => this.waitingQueue.push(resolve));
      }
      this.locked = true;
      return new MutexGuard(this);
    }

    unlock (): void {
      if (!this.locked) {
        throw new Error("Mutex is not locked");
      }
      this.locked = false;
      if (this.waitingQueue.length > 0) {
        const resolve = this.waitingQueue.shift();
        resolve!();
      }
    }
  }
  • 实现MutexGuard
class MutexGuard<T> {
    private mutex: Mutex<T>

    constructor(mutex: Mutex<T>) {
      this.mutex = mutex;
    }

    async lock (): Promise<MutexGuard<T>> {
      await this.mutex.lock();
      return this;
    }

    unlock (): void {
      this.mutex.unlock();
    }

    get value (): T {
      return this.mutex.value;
    }

    set value (newValue: T) {
      this.mutex.value = newValue;
    }
  }
  • 使用MutexMutexGuard
 async function main () {
    const counter = new Mutex(0);

    async function incrementCounter () {
      const guard = await counter.lock();
      try {
        const oldValue = guard.value;
        await new Promise<void>((resolve) => setTimeout(resolve, Math.random() * 1000));
        guard.value = oldValue + 1;
        console.log(`Incremented counter to ${guard.value}`);
      } finally {
        guard.unlock();
      }
    }

    const tasks: Array<Promise<void>> = [];
    for (let i = 0; i < 5; i++) {
      tasks.push(incrementCounter());
    }
    await Promise.all(tasks);

    console.log(`Final counter value: ${(await counter.lock()).value}`);
  }
main()
  • 输出
$ bun index.ts 
Incremented counter to 1
Incremented counter to 2
Incremented counter to 3
Incremented counter to 4
Incremented counter to 5
Final counter value: 5