<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [轉(zhuǎn)] 并發(fā)與并行

      作者:謝杰

      該文章是并發(fā)異步操作系列文章第四篇。

      今天我們來(lái)解決一個(gè)很多同學(xué)經(jīng)常搞混的概念對(duì):并發(fā)和并行。

      這兩個(gè)詞在日常交流中常常被混用,但在編程領(lǐng)域,它們指的是完全不同的執(zhí)行模式。理解它們的區(qū)別,不僅能幫你正確選型,還能在調(diào)優(yōu)性能時(shí)少走彎路。

      先拋一個(gè)問(wèn)題:

      你一邊刷手機(jī)一邊等外賣,這是并發(fā)還是并行?

      如果你下意識(shí)覺得“反正就是同時(shí)干兩件事”,那這篇文章你一定要看完。我會(huì)用簡(jiǎn)單的比喻和直觀的例子讓你徹底搞清這兩個(gè)概念,并且一起看一下 JS 中哪些特性和這兩個(gè)概念相關(guān)。

      在計(jì)算機(jī)領(lǐng)域,“并發(fā)”和“并行”并不是同義詞,雖然它們都能讓你在同一時(shí)間段內(nèi)處理多個(gè)任務(wù),但實(shí)現(xiàn)方式、依賴條件和結(jié)果體驗(yàn)都不一樣。

      并發(fā)

      英語(yǔ)為 Concurrency,指的是在同一時(shí)間段內(nèi),多個(gè)任務(wù)交替進(jìn)行。這些任務(wù)沒(méi)有真正同時(shí)運(yùn)行,而是通過(guò)任務(wù)切換來(lái)營(yíng)造“同時(shí)進(jìn)行”的效果。

      • 類比:一個(gè)服務(wù)員同時(shí)負(fù)責(zé) 3 桌客人,他會(huì)先給 A 桌上菜,再去 B 桌點(diǎn)單,然后回到 C 桌加水……看起來(lái)好像在同時(shí)照顧三桌,其實(shí)是快速切換任務(wù)
      • 特點(diǎn):一個(gè)執(zhí)行單元(單線程)通過(guò)任務(wù)調(diào)度來(lái)處理多個(gè)任務(wù)。

      并行

      英語(yǔ)為 Parallelism,指的是在同一時(shí)刻,多個(gè)任務(wù)真正同時(shí)運(yùn)行。這通常依賴于多核 CPU多臺(tái)機(jī)器的同時(shí)執(zhí)行。

      • 類比:3 個(gè)服務(wù)員分別負(fù)責(zé) 3 桌客人,大家同時(shí)干活,互不干擾,這就是真正的同時(shí)進(jìn)行
      • 特點(diǎn):需要多個(gè)執(zhí)行單元(多線程、多進(jìn)程、多核硬件)共同工作。

      兩者具體的對(duì)比如下表:

      維度 并發(fā)(Concurrency) 并行(Parallelism)
      定義 多個(gè)任務(wù)在同一時(shí)間段交替執(zhí)行 多個(gè)任務(wù)在同一時(shí)刻真正同時(shí)執(zhí)行
      實(shí)現(xiàn)方式 單線程任務(wù)切換、事件循環(huán)、調(diào)度器 多線程、多進(jìn)程、多核 CPU 同時(shí)執(zhí)行
      硬件依賴 無(wú)需多核,可在單核 CPU 上實(shí)現(xiàn) 通常需要多核 CPU 或多臺(tái)機(jī)器
      類比 一個(gè)服務(wù)員輪流服務(wù)多桌客人 多個(gè)服務(wù)員同時(shí)服務(wù)多桌客人
      優(yōu)勢(shì) 節(jié)省資源、實(shí)現(xiàn)簡(jiǎn)單 性能強(qiáng)、適合 CPU 密集型任務(wù)
      劣勢(shì) CPU 密集任務(wù)下切換開銷大 實(shí)現(xiàn)復(fù)雜、線程/進(jìn)程通信開銷大

      理解清楚核心的概念后,接下來(lái)我們就需要看一下兩者在 JS 中的實(shí)現(xiàn)方式了。

      并發(fā)

      JavaScript 是一門單線程語(yǔ)言,同一時(shí)刻只有一個(gè)任務(wù)在運(yùn)行。那遇到耗時(shí)長(zhǎng)的任務(wù)怎么辦?——如果阻塞在原地等待,整個(gè)頁(yè)面就會(huì)“卡死”,用戶無(wú)法進(jìn)行任何操作。

      解決辦法

      把這些耗時(shí)任務(wù)交給環(huán)境中的異步機(jī)制去處理(例如 I/O 操作、定時(shí)器、網(wǎng)絡(luò)請(qǐng)求等),等任務(wù)完成后再通過(guò)事件循環(huán)(Event Loop)將回調(diào)推回主線程繼續(xù)執(zhí)行。

      因此,在 JS 中,執(zhí)行異步任務(wù)其實(shí)就是一種并發(fā)的表現(xiàn):多個(gè)任務(wù)在同一時(shí)間段內(nèi)交替推進(jìn)(本質(zhì)是時(shí)間片切換),看起來(lái)就像“同時(shí)”在進(jìn)行。

      JS 常見的異步寫法演進(jìn)

      下面的 4 個(gè)階段,是多數(shù)開發(fā)者在學(xué)習(xí)和使用異步時(shí)常見的寫法演進(jìn)(實(shí)際發(fā)布時(shí)間線有部分重疊):

      1. 回調(diào)函數(shù)

      最早的異步模式,通過(guò)將邏輯寫在回調(diào)函數(shù)中實(shí)現(xiàn)任務(wù)完成后的操作。

      console.log("開始");
      
      setTimeout(() => {
        console.log("任務(wù)完成");
      }, 1000);
      
      console.log("結(jié)束");
      
      1. Promise

      Promise 讓異步代碼更可讀,避免了“回調(diào)地獄”。

      new Promise((resolve) => {
        setTimeout(() => resolve("任務(wù)完成"), 1000);
      }).then(console.log);
      
      console.log("繼續(xù)執(zhí)行其他任務(wù)");
      
      1. 生成器

      生成器可以通過(guò) yield 暫停執(zhí)行,并與異步邏輯結(jié)合。通常需要配合調(diào)度器(如 co 庫(kù))自動(dòng)迭代,否則需要手動(dòng)調(diào)用 next()

      function* task() {
        const result = yield new Promise((resolve) =>
          setTimeout(() => resolve("任務(wù)完成"), 1000)
        );
        console.log(result);
      }
      
      const iterator = task();
      iterator.next().value.then((res) => iterator.next(res));
      
      1. async/await

      async/await 是 Promise 的語(yǔ)法糖,讓異步代碼看起來(lái)像同步代碼。

      async function run() {
        await new Promise((resolve) => setTimeout(resolve, 1000));
        console.log("任務(wù)完成");
      }
      
      run();
      

      并發(fā)操作的常用 API

      在實(shí)際開發(fā)中,并發(fā)操作更多是結(jié)合以下 Promise API 來(lái)實(shí)現(xiàn)(這些方法在前文已有詳細(xì)講解,這里僅列出名稱與核心特性):

      1. Promise.all:需要所有任務(wù)成功才能繼續(xù)(典型場(chǎng)景:并行請(qǐng)求多個(gè)接口)
      2. Promise.allSettled:不在乎成敗,只想收集所有結(jié)果
      3. Promise.race:獲取最先完成的任務(wù)(可用于實(shí)現(xiàn)超時(shí)控制)
      4. Promise.any:容錯(cuò)性強(qiáng),只要有一個(gè)成功即可

      總結(jié)一下,在 JS 中,并發(fā)是通過(guò)異步調(diào)度實(shí)現(xiàn)的,本質(zhì)上是一個(gè)線程在不同任務(wù)之間交替執(zhí)行,利用事件循環(huán)在空閑時(shí)處理等待完成的任務(wù)。這種方式雖然看起來(lái)像是“同時(shí)”進(jìn)行,但在任何一個(gè)時(shí)間點(diǎn),主線程實(shí)際上只在執(zhí)行一個(gè)任務(wù)。

      如果我們希望多個(gè)任務(wù)能夠真正地同時(shí)運(yùn)行,而不是依靠時(shí)間片切換,這則是我們接下來(lái)要討論的重點(diǎn):并行

      并行

      前面已經(jīng)解釋了并行的概念,JS 作為一門單線程的語(yǔ)言,本身是不能直接并行執(zhí)行代碼的,但它可以借助額外的線程來(lái)實(shí)現(xiàn)并行,比如:

      • 瀏覽器環(huán)境:Web Worker
      • Node.js 環(huán)境:Worker Threads

      這類 API 的本質(zhì),是通過(guò)在后臺(tái)開辟新的線程去運(yùn)行代碼,從而讓多個(gè)計(jì)算任務(wù)真正同時(shí)進(jìn)行,并且通過(guò)消息機(jī)制與主線程通信。

      注意:無(wú)論是 Web Worker 還是 Worker Threads,涉及到的細(xì)節(jié)非常非常多,隨便哪一個(gè)單獨(dú)領(lǐng)出來(lái),都可以寫成一個(gè)新的系列文章。所以這里只需要了解這兩者是實(shí)現(xiàn)并行的手段即可。

      Web Worker

      主線程只有一個(gè),CPU 密集型任務(wù)(圖像處理、加密、路徑規(guī)劃、壓縮/解壓等)會(huì)阻塞 UI。Web Worker 把重計(jì)算挪到后臺(tái)線程執(zhí)行,主線程繼續(xù)保持交互與渲染,實(shí)現(xiàn)真正的并行(與異步并發(fā)的時(shí)間片切換不同)。

      Worker 是一個(gè)獨(dú)立的 JS 線程,沒(méi)有 DOM、windowdocument;與主線程通過(guò) postMessage/onmessage 傳遞消息。適合“計(jì)算重、輸入輸出輕”的場(chǎng)景;I/O 為主的任務(wù)通常不必用 Worker。

      下面來(lái)看一個(gè) Dedicated Worker 的最小可用示例:

      Dedicated Worker 意思是“專用 Web Worker”,只服務(wù)于創(chuàng)建它的那一個(gè)頁(yè)面(或腳本)的 Worker 線程。這個(gè) Worker 不會(huì)被其他頁(yè)面/標(biāo)簽復(fù)用或共享。

      除了 Dedicated Worker 以外,常見的還有:

      • SharedWorker(共享 Worker):可被同源的多個(gè)頁(yè)面/ iframe 共享,一個(gè)實(shí)例多處連接;用 new SharedWorker('./shared.js'),通過(guò) MessagePort 通信、onconnect 事件接入。適合跨標(biāo)簽共享連接/緩存/池化。

      • Service Worker:不是計(jì)算線程,而是網(wǎng)絡(luò)代理層(離線緩存、請(qǐng)求攔截、推送),用 navigator.serviceWorker.register() 注冊(cè),按生命周期事件運(yùn)行,不是拿來(lái)做重計(jì)算的。

      // main.js
      
      // 使用 ESM worker 便于打包器處理依賴
      const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
      
      worker.onmessage = (e) => {
        const { id, result } = e.data;
        console.log(`任務(wù) ${id} 完成:`, result);
      };
      
      worker.onerror = (err) => console.error('Worker 出錯(cuò):', err.message);
      
      // 發(fā)送計(jì)算任務(wù)(演示 Transferable:零拷貝轉(zhuǎn)移 ArrayBuffer)
      let seq = 0;
      export function sumLargeArray(ints) {
        const id = ++seq;
        const buf = new ArrayBuffer(ints.length * 4);
        new Int32Array(buf).set(ints);
        worker.postMessage({ id, op: 'sum', buf }, [buf]); // 發(fā)送并轉(zhuǎn)移所有權(quán)
      }
      
      // worker.js
      
      self.onmessage = (e) => {
        const { id, op, buf } = e.data;
        if (op !== 'sum') return;
      
        const view = new Int32Array(buf);
        let s = 0;
        for (let i = 0; i < view.length; i++) s += view[i];
      
        // 回傳結(jié)果
        postMessage({ id, result: s });
      };
      

      上面的示例中:

      • new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }) 這寫法,是讓打包器找得到入口、按 ESM 處理依賴,避免構(gòu)建后路徑翻車。

      • 每個(gè)任務(wù)都有一個(gè) id,這樣信息的“發(fā)送”和“回包”能對(duì)上號(hào),高并發(fā)也不串臺(tái)。

      • postMessage({ id, op: 'sum', buf }, [buf]) 里的第二個(gè)參數(shù)是 transfer list:把 ArrayBuffer 的所有權(quán)直接過(guò)戶給 worker,零拷貝更快。注意:過(guò)戶后主線程的 buf 就是空殼了,別再用。

      零拷貝的意思是不再?gòu)?fù)制一份數(shù)據(jù),而是把“這塊內(nèi)存的使用權(quán)”直接交給對(duì)方,或雙方直接共享同一塊內(nèi)存。

      在 JS 里的兩種典型方式:

      1. Transferable(轉(zhuǎn)移所有權(quán)):把 ArrayBuffer(或 MessagePortImageBitmapOffscreenCanvas 等)放進(jìn) postMessage 的第二個(gè)參數(shù)(transfer list)里。發(fā)送后,原端的緩沖區(qū)會(huì)被“剝離”(detached),對(duì)端拿到同一塊字節(jié)的控制權(quán),避免再做一份拷貝。
      2. SharedArrayBuffer(共享內(nèi)存):雙方拿到的是同一塊內(nèi)存的視圖(再也不用傳來(lái)傳去),配合 Atomics 做同步。瀏覽器環(huán)境里要滿足 cross-origin isolation;Node.js 環(huán)境里直接可用。
      • worker.onerror 兜底,腳本加載失敗、運(yùn)行時(shí)未捕獲異常等都會(huì)觸發(fā);onmessageerror 負(fù)責(zé)消息反序列化失敗這類問(wèn)題。

      • 可以使用 worker.terminate() 釋放線程;如果是長(zhǎng)駐后臺(tái)計(jì)算,可以復(fù)用同一個(gè) worker 來(lái)批量處理任務(wù)。

      Worker Threads

      瀏覽器里是 Web Workers,到了 Node.js 環(huán)境,并行就交給 Worker Threads。它把CPU 密集型計(jì)算從主線程(事件循環(huán))里剝離到真實(shí)的操作系統(tǒng)線程里跑,避免把整臺(tái)服務(wù)“卡住”。和 child_process 不同的是:線程共享進(jìn)程內(nèi)存,可以用 SharedArrayBuffer/Atomics,也能把 ArrayBuffer 作為 Transferable 零拷貝傳遞。

      下面是一個(gè)最小可用示例(ESM):

      // main.mjs
      import { Worker } from 'node:worker_threads';
      
      // 和瀏覽器那段保持同樣的“發(fā)任務(wù)→回包”模式
      const worker = new Worker(new URL('./worker.mjs', import.meta.url), {
        type: 'module',
      });
      
      let seq = 0;
      function sumLargeArray(ints) {
        const id = ++seq;
        const buf = new ArrayBuffer(ints.length * 4);
        new Int32Array(buf).set(ints);
      
        // Node 里的 worker.postMessage 同樣支持 transferList
        worker.postMessage({ id, op: 'sum', buf }, [buf]);
      }
      
      worker.on('message', ({ id, result, error }) => {
        if (error) return console.error(`任務(wù) ${id} 失敗:`, error);
        console.log(`任務(wù) ${id} 完成:`, result);
      });
      
      worker.on('error', (err) => {
        console.error('Worker 線程錯(cuò)誤:', err);
      });
      
      worker.on('exit', (code) => {
        if (code !== 0) console.warn('Worker 非正常退出,code =', code);
      });
      
      // demo:丟一個(gè)大數(shù)組過(guò)去
      sumLargeArray(Int32Array.from({ length: 1e6 }, (_, i) => i));
      
      // worker.mjs
      import { parentPort } from 'node:worker_threads';
      
      parentPort.on('message', ({ id, op, buf }) => {
        try {
          if (op !== 'sum') return;
      
          const view = new Int32Array(buf);
          let s = 0;
          for (let i = 0; i < view.length; i++) s += view[i];
      
          parentPort.postMessage({ id, result: s });
        } catch (e) {
          parentPort.postMessage({ id, error: String(e) });
        }
      });
      

      在上面的代碼示例中:

      • 同樣的心智模型:主線程 postMessage → { id } 發(fā)任務(wù);worker 里計(jì)算后 postMessage 回來(lái),用 id 對(duì)號(hào)入座。
      • 零拷貝傳輸:把 ArrayBuffer 放進(jìn) transferList,直接“過(guò)戶”到 worker,避免復(fù)制。
      • 線程而非進(jìn)程:對(duì)比 child_process,線程更輕、更易共享內(nèi)存,但也要注意不要破壞共享狀態(tài)(必要時(shí)用 Atomics 做同步)。
      • 生命周期:worker.terminate() 會(huì)返回一個(gè) Promise;同時(shí)監(jiān)聽 error/exit,方便回收與自愈。
      • 路徑與模塊:和瀏覽器那段一樣,用 new URL('./worker.mjs', import.meta.url) + { type: 'module' },避免構(gòu)建/部署后路徑失效。

      寫在最后

      并發(fā)和并行是現(xiàn)代 JS 工程里反復(fù)出現(xiàn)的主題,總結(jié)一下:

      • 并發(fā)(Concurrency):一個(gè)線程在任務(wù)間切換;適合 I/O、等待型工作(網(wǎng)絡(luò)/磁盤/定時(shí)器)。JS 的異步任務(wù)處理,采用的就是并發(fā)模型。并發(fā)像一個(gè)人來(lái)回切活兒。
      • 并行(Parallelism):多線程/多核真正同時(shí)運(yùn)行;適合 CPU 密集(圖像處理、加解密、路徑規(guī)劃、壓縮等)。JS 中實(shí)現(xiàn)并行,瀏覽器環(huán)境用 Web Workers;Node.js 環(huán)境用 Worker Threads。并行是請(qǐng)來(lái)更多人同時(shí)干。

      兩者不沖突,絕大多數(shù)前端/服務(wù)端場(chǎng)景用并發(fā)異步就夠了,只有當(dāng)計(jì)算真把 CPU 吃滿、拖慢交互或吞吐時(shí),再讓 Worker 家族登場(chǎng)。


      -EOF-

      posted @ 2025-10-30 14:35  Zhentiw  閱讀(11)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 国产仑乱无码内谢| 久久夜色精品国产亚洲a| 国产真人做受视频在线观看| 国产精品无码无需播放器| 东京热一精品无码av| 亚洲AV日韩AV激情亚洲| 高清无码爆乳潮喷在线观看| 亚洲av不卡电影在线网址最新| 国产精品高清中文字幕| 欧美无人区码suv| 精品一区二区三区少妇蜜臀| 久久精品国产亚洲av麻豆软件| 麻豆妓女爽爽一区二区三| 久久精品国产国产精品四凭| 亚洲精品国产中文字幕| 性久久久久久| 国产乱色熟女一二三四区| 亚洲女人天堂| 国产一区二区高清不卡| 日韩精品福利一区二区三区| 久久成人国产精品免费软件| 亚洲色最新高清AV网站| 久热这里只有精品视频3| 亚洲精品第一国产综合精品| 噜噜噜噜私人影院| 日本少妇自慰免费完整版| 无码人妻日韩一区日韩二区| 国产suv精品一区二区四| 中文字幕网红自拍偷拍视频| 麻豆国产成人AV在线播放| 亚洲欧洲日产国无高清码图片| 国产精品天天狠天天看| 99精品热在线在线观看视| 久久人妻精品大屁股一区| 中文字幕一区日韩精品| 蜜臀av久久国产午夜| 色翁荡息又大又硬又粗又视频图片| 午夜福利激情一区二区三区| 亚洲精品揄拍自拍首页一| 国产亚洲一二三区精品| 国产亚洲精品久久77777|