JS信号量模式实现并发请求控制

285 阅读1分钟

信号量(Semaphore)是一种并发控制机制,用于限制可同时访问某个资源的线程数。在编程中,信号量是一种计数器,用于控制对资源的访问。它的作用类似于锁,但更为灵活。

信号量基本概念

1、计数器 2、P操作(等待) 3、V操作(释放)

信号量模式的应用

信号量模式在编程中应用广泛,常用于控制对共享资源的访问,例如:

  • 限制同时访问某个资源的线程或进程数量。
  • 控制并发任务的执行数量。
  • 实现生产者-消费者模型。

在 JavaScript 中,我们可以使用 async/await 和 Promise 来实现信号量模式,以控制并发请求的数量。实现代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Semaphore</title>
  </head>
  <body>
    <script>
    /**
     * 核心工作流程:
     *  - 增加计数器:队列插入 resolve 回调
     *  - 检查队列: 如果请求队列中有等待的请求,消费计数器 ,执行resolve 回调
     */
      class Semaphore {
        constructor(maxConcurrent) {
          this.maxConcurrent = maxConcurrent;
          this.currentCount = 0;
          this.queue = [];
        }

        async acquire() {
          if (this.currentCount < this.maxConcurrent) {
            this.currentCount++;
            return;
          }
          // 如果当前并发数达到最大值,则创建一个新的 Promise,并将其 `resolve` 方法放入队列,那么这个 Promise 就会一直处于pending状态,即新请求进入等待状态
          return new Promise(resolve => this.queue.push(resolve));
        }

        release() {
          if (this.queue.length > 0) {
            // 检查队列: 如果队列中有等待的请求,取出队列中的第一个回调函数,即 上面存入的 `resolve` 方法,并执行。那么 Promise 得以被解决,从而解除阻塞状态
            const next = this.queue.shift();
            next();
          } else {
            this.currentCount--;
          }
        }
      }

      const test = new Semaphore(5);

      const makeRequest = async url => {
        await test.acquire();

        try {
          const response = await fetch(url);
          const data = await response.json();
          return data;
        } finally {
          test.release();
        }
      };

      const runRequest = async urls => {
        const results = await Promise.all(urls.map(url => makeRequest(url)));
        console.log('results: ' + results);
      };
      const urls = [
        'https://api.example.com/data1',
        'https://api.example.com/data2',
        'https://api.example.com/data3',
        'https://api.example.com/data4',
        'https://api.example.com/data5',
        'https://api.example.com/data6',
        'https://api.example.com/data7',
      ];
      runRequest(urls);
    </script>
  </body>
</html>