掌握 TypeScript 中的并发和并行

现代应用程序需要高性能和响应能力,这要求开发人员掌握并发性和并行性。TypeScript 作为 JavaScript 的超集,提供了强大的工具和模式来管理这些复杂性。本指南从各个角度探讨了这两个概念,深入探讨了在 TypeScript 中利用并发性和并行性的实际示例、模式和高级实践。

并发与并行:主要区别

在开始编写代码之前,理解以下术语至关重要:

**1.并发性:**

  • 定义:系统通过交叉执行(不一定同时执行)来处理多个任务的能力。
  • 示例:在事件循环中在处理数据库查询和处理文件上传之间切换。
  • **2.并行性:**

  • 定义:利用多核处理器同时执行多项任务。
  • 示例:在不同的核心上同时执行复杂的数学计算。
  • **可视化:**

    想象一个餐厅:

  • 并发:一位厨师同时处理多道菜肴。
  • 并行:多位厨师同时制作不同的菜肴。
  • TypeScript 中的并发

    JavaScript 以及 TypeScript 都运行在 **单线程事件循环** 上,这听起来可能让并发变得不可能。然而,并发是通过 **回调**、**承诺** 和 **异步/等待** 等异步编程模型实现的。

    **1. 使用 Promises 实现并发**

    Promises 是 TypeScript 中实现并发的最简单方法之一。

    const fetchData = (url: string) => {
      return new Promise((resolve) => {
        setTimeout(() => resolve(`Data from ${url}`), 1000);
      });
    };
    
    const main = async () => {
      console.log('Fetching data concurrently...');
      const data1 = fetchData('https://api.example.com/1');
      const data2 = fetchData('https://api.example.com/2');
    
      const results = await Promise.all([data1, data2]);
      console.log(results); // ["Data from https://api.example.com/1", "Data from https://api.example.com/2"]
    };
    main();

    **解释:**

  • Promise.all 允许两个提取操作同时运行,从而节省时间。 2. 使用 Async/Await 进行并发 async/await 简化了承诺链,同时保持了异步特性。
  • async function task1() {
      console.log("Task 1 started");
      await new Promise((resolve) => setTimeout(resolve, 2000));
      console.log("Task 1 completed");
    }
    
    async function task2() {
      console.log("Task 2 started");
      await new Promise((resolve) => setTimeout(resolve, 1000));
      console.log("Task 2 completed");
    }
    
    async function main() {
      console.log("Concurrent execution...");
      await Promise.all([task1(), task2()]);
      console.log("All tasks completed");
    }
    main();

    TypeScript 中的并行性

    尽管 JavaScript 本身不支持多线程,但 Web Workers 和 Node.js Worker Threads 可以实现并行。这些功能利用单独的线程来处理计算量大的任务。

    **1. 用于并行的 Web Worker**

    在浏览器环境中,Web Workers 在单独的线程中执行脚本。

    // worker.ts
    addEventListener('message', (event) => {
      const result = event.data.map((num: number) => num * 2);
      postMessage(result);
    });
    // main.ts
    const worker = new Worker('worker.js');
    
    worker.onmessage = (event) => {
      console.log('Result from worker:', event.data);
    };
    
    worker.postMessage([1, 2, 3, 4]);

    **2. Node.js 工作线程**

    对于服务器端应用程序,Node.js 提供了“worker_threads”。

    // worker.js
    const { parentPort } = require('worker_threads');
    parentPort.on('message', (data) => {
      const result = data.map((num) => num * 2);
      parentPort.postMessage(result);
    });
    // main.js
    const { Worker } = require('worker_threads');
    
    const worker = new Worker('./worker.js');
    worker.on('message', (result) => {
      console.log('Worker result:', result);
    });
    worker.postMessage([1, 2, 3, 4]);

    有效并发和并行的模式

    **1. 用于管理并发的任务队列**

    当处理许多任务时,任务队列可确保受控执行。

    class TaskQueue {
      private queue: (() => Promise)[] = [];
      private running = 0;
      constructor(private concurrencyLimit: number) {}
    
      enqueue(task: () => Promise) {
        this.queue.push(task);
        this.run();
      }
    
      private async run() {
        if (this.running >= this.concurrencyLimit || this.queue.length === 0) return;
    
        this.running++;
        const task = this.queue.shift();
        if (task) await task();
        this.running--;
        this.run();
      }
    }
    
    // Usage
    const queue = new TaskQueue(3);
    for (let i = 0; i < 10; i++) {
      queue.enqueue(async () => {
        console.log(`Task ${i} started`);
        await new Promise((resolve) => setTimeout(resolve, 1000));
        console.log(`Task ${i} completed`);
      });
    }

    **2. 使用工作池进行负载平衡**

    工作池有效地在多个工作器之间分配任务。

    import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
    
    if (isMainThread) {
      const workers = Array.from({ length: 4 }, () => new Worker(__filename));
      const tasks = [10, 20, 30, 40];
      workers.forEach((worker, index) => {
        worker.postMessage(tasks[index]);
        worker.on('message', (result) => console.log('Result:', result));
      });
    } else {
      parentPort.on('message', (task) => {
        parentPort.postMessage(task * 2);
      });
    }

    挑战与解决方案

    **1. 调试异步代码**

  • 使用 Node.js 中的 async_hooks 之类的工具来跟踪异步操作。
  • 使用支持调试异步/等待代码的 IDE。
  • **2. 错误处理**

  • 将承诺包装在 try/catch 块中或将 .catch() 与 Promise.all 一起使用。
  • **3. 竞争条件**

    避免共享状态或使用锁定机制。

    并发和并行的最佳实践

    **1. 优先考虑异步 I/O:**避免阻塞主线程进行 I/O 密集型操作。

    **2. 使用工作线程执行 CPU 密集型任务:**将繁重的计算任务卸载到工作线程或 Web 工作线程上。

    **3. 限制并发**:使用任务队列或类似“p-limit”的库来控制并发级别。

    **4. 利用库:**使用像“Bull”这样的库进行任务队列或“Workerpool”进行工作线程管理。

    **结论**

    并发性和并行性对于构建高性能、可扩展的 TypeScript 应用程序至关重要。并发性通过交错任务来提高响应能力,而并行性则可以在多核系统上同时执行。通过掌握这些概念,开发人员可以应对现代应用程序中的挑战并提供无缝的用户体验。

    我的个人网站:https://shafayet.zya.me

    这是如何关闭 Vim...😭😭😭

    Image description