亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

微前端架構ModuleFederationPlugin源碼解析

 更新時間:2022年11月01日 14:41:45   作者:echolc55873  
這篇文章主要為大家介紹了微前端架構ModuleFederationPlugin源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

序言

本文是 Webpack ModuleFederationPlugin(后面簡稱 MF) 源碼解析 文章中的第一篇,在此系列文章中,我將帶領大家抽絲剝繭、一步步地去解析 MF 源碼。當然為了幫助大家理解,可能中間也會涉及到 Webpack 源碼中的其它實現,我會根據情況或淺或深的一并進行講解。因為看 Webpack 源碼需要掌握的知識量非常大,所以為了更好理解文章中的內容,你最好有如下 Webpack 相關的背景知識:

  • 對 Webpack 核心的數據結構:Dependency、Module、Chunk 等有基本的認識
  • 了解 Webpack 中的插件機制,對基于 tabpable 的 Hooks 機制有一定的了解,如果寫過 Webpack 插件就更好了
  • 看過 MF 的官方文檔,對其帶來的關鍵性作用有基本的認識,如果了解一些其應用場景就更好了

話不多說,讓我們開始正文。

背景

先簡單說一下為什么要去閱讀 MF 的源碼,我個人理解閱讀源碼有兩個原因:

一,它的實現非常優(yōu)秀,通過閱讀源碼能學習一些設計思想和編程技巧;

二,工作或者自己的項目使用到了,但是官方給的文檔不太夠,遇到問題無論最后有沒有解決,都有點摸不著頭腦,閱讀源碼是為了更好地了解其內部實現,遇到問題更容易 debug。

而我閱讀 MF 的源碼,主要是出于第二種目的,當然我個人對 Webpack 也是非常感興趣。目前我們部門 B 端的產品是基于 MF 實現的微前端架構,而我主要負責 B 端的開發(fā)以及參與 B 端性能優(yōu)化專項,今年大部分時間都是跟 MF “搏斗”。

雖然到目前為止,性能優(yōu)化已經獲得了一些階段性的勝利,但是實際上在這個過程中,我們還是走了很多彎路。這些彎路不少是由于對 MF 內部的實現細節(jié)不夠了解導致的,當然除此之外,我們還需要建立一套規(guī)范的 MF 標準化開發(fā)流程。所以,閱讀 MF 源碼對于我個人來說非常有必要。

首先,我們先簡單了解下 MF 相關知識。

MF 基本介紹

首先,MF 是一個 Webpack 的官方插件,在 Webpack 生態(tài)中有茫茫多的插件中,好像一個插件有點微不足道。但是,MF 的作者稱其為 “A game-changer in JavaScript architecture”,當然從構建工具的角度來講,有點言過其實,因為它只能用于 Webpack 中。

但是從它帶來的 JavaScript 架構設計上的理念:遠程依賴共享(復用組件或者其它邏輯), 我覺得其實是給前端帶來新的思考視角。

以前我們復用組件或者邏輯主要的方式有:

  • 抽離一個 NPM 包,從維護性和復用性角度來講,是目前最常見的方式。缺點在于,在微前端架構中,如果 fix 了一個 NPM 包問題,那么每一個應用都需要升級版本,重新構建打包部署上線,多團隊開發(fā)的時候非常低效;
  • 將產物打包成 UMD 的格式,然后通過 CDN 的方式能一定程度解決重新構建打包上線的問題,但是隨著復用的組件和邏輯越多,可能會引入很多多余的 chunk 問題(如果對性能有很高的要求) 。比如 A 和 B 組件同時依賴了 lodash,那么打包成 UMD 格式有多余的 lodash chunk,沒法復用。

我們來看下 MF 是怎么解決這個問題的。首先看一個簡單的 MF 使用的例子,假設我們現在有兩個應用 app1 和 app2:

// app1 webpack.config.js
module.exports = {
  // 省略其它配置
	plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      remotes: {
        app2: 'app2@http://localhost:3002/remoteEntry.js',
      },
      exposes: {
        './input': './src/components/Input'
      },
      shared: {
        'react': {
          singleton: true, 
          requiredVersion: require('./package.json').dependencies.react
        },
        'react-dom': {
          singleton: true,
          requiredVersion: require('./package.json').dependencies['react-dom']
        },
        'lodash': {
          requiredVersion: require('./package.json').dependencies['lodash'],
          singleton: true,
        }
      }
    }),
  ] 
}
// app1 src/components/Input.tsx
import * as React from 'react'
import { Input } from 'antd'
export default function WrapperInput () {
  return (
    <div>
      app1 input: <Input />
    </div>
  )
}
// app1 src/App.tsx
import { Input } from 'antd';
import * as React from 'react';
const RemoteButton = React.lazy(() => import('app2/Button'));
const App = () => (
  <div>
    <h1>Typescript</h1>
    <h2>App 1</h2>
    <React.Suspense fallback="Loading Button">
      <RemoteButton />
    </React.Suspense>
    <div>
      <Input />
    </div>
  </div>
);
export default App;    

app2 的部分代碼:

// app2 webpack.config.js
module.exports = {
  // 省略其它配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'app2',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/Button',
      },
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
      },
  }),
}
// app2 src/Button.tsx
import * as React from 'react';
const Button = () => <button>App 3 Button</button>;
export default Button;
// app2 src/App.tsx
import * as React from 'react';
import LocalButton from './Button';
import RemoteInput from 'app1/input';
const App = () => (
  <div>
    <h1>Typescript</h1>
    <h2>App 3</h2>
    <LocalButton />
    <React.Suspense fallback={null}>
      <RemoteInput />
    </React.Suspense>
  </div>
);
export default App;

最后實現的效果:

簡單的 Webpack 配置,我們就可以實現 app1 和 app2 兩個應用之間的組件遠程共享,從代碼看,我們知道 app1 依賴了 app2 的 Button 組件,而 app2 依賴了 app1 的 Input 組件。

當然不止如此,MF 還可以做到:

  • 依賴復用, app1 和 app2 同時依賴了 react 和 react-dom,那我們可以在雙方的 Webpack 配置中,將兩個依賴配置成 shared,而且通過 requiredVersion 指定版本;
  • 微前端架構,微前端架構有很多實現的方式,比如 iframe、web-component 等,但是 MF 的出現,使得實現一套微前端的架構更加簡單,也能非常容易解決微前端架構中的一些組件復用問題、頻繁構建部署上線問題;
  • 支持服務端渲染,MF 的實現不依賴瀏覽器,同樣的代碼,只需要將 Webpack 配置中的target改成

node,那么構建的產物就能支持 SSR。

到這里,讀者已經對 MF 的使用和定位有了基本的印象,根據 MF 帶來的全新的復用能力,我們可以做一些應用場景的思考。

應用場景

微前端架構

微前端是這幾年比較火的一個前端應用架構方案,其中比較核心的一點是各子應用之間要做到獨立開發(fā),獨立構建部署上線。 從上一節(jié)對 MF 的介紹中,我們發(fā)現它天然就已經有這個優(yōu)勢,因此為了設計一個基于 MF 的微前端架構,我們要解決的第一點是子應用之間需要有個類似中心化的服務,將其它子應用的服務地址下發(fā)給需要消費的子應用;第二點,我們要解決子應用之間的一些通信問題,例如共享的一些用戶狀態(tài)。 當然還有一些其它問題,例如 UI 一致性問題。

基于以上的問題,我們可以很容易想到一種非常經典的微前端架構方案,那就是基于一個基座服務的中心化的架構方式。

每個 APP 都是一個子應用,這里可以有兩種方式:如果完全不需要依賴基座的狀態(tài),則可以做成一個更加通用的前端服務,只作為提供方,在 MF 中也稱為 remotes 應用。如果需要依賴主應用的狀態(tài),或者說只導出路由讓基座幫忙注冊,這樣就可以共享基座的所有狀態(tài),這種方式與我們現在 B 端的架構方式類似。這樣的架構方式,也能通過 MF shared 機制鎖定 UI 庫的版本,保證所有子應用 UI 的一致性。

服務化的 library 和 components

跳出微前端架構,假設我們現在的場景是維護一個巨型前端應用,我們發(fā)現隨著頁面和依賴的第三方依賴逐漸增多,那么每次開發(fā)構建部署上線的時長也會不斷增加。雖然 Webpack v5+ 版本已經做了很多優(yōu)化例如本地緩存,但是對于巨型應用,我們還是發(fā)現構建還是非常低效。于是,基于 MF 的能力,我們可以做這樣的一個架構設計:

我們可以將平時使用的第三方庫和組件庫,分別做成一個單獨的服務,如果部門技術棧統(tǒng)一的項目可以通過 MF 插件遠程使用這兩個服務,這樣無論是開發(fā)時還是上線構建都可以省掉這部分的構建時間,一定程度上提高了開發(fā)效率。

MF 的使用姿勢非常靈活,你可以根據開發(fā)需要,充分挖掘更多的使用場景。MF 介紹的部分就到這里,下面我們正式進入源碼解析的內容。

ModuleFederationPlugin 源碼解析

入口源碼

MF 插件相關的源碼放在 lib/container 下,我們首先看下 lib/container/ModuleFedration.js的代碼:

// 省略一些 import 代碼
class ModuleFederationPlugin {
	/**
	 * @param {ModuleFederationPluginOptions} options options
	 */
	constructor(options) {
		validate(options);
		this._options = options;
	}
	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		const { _options: options } = this;
		// expose 模塊編譯產物導出的類型,選項有 var、umd、commonjs、module 等,跟 output 配置中的 library 作用是一樣的
		// var 代表輸出的模塊是掛在 window 對象上
		const library = options.library || { type: "var", name: options.name };
		// remote 模的類型,選項有 var、umd、commonjs、module 等,跟 output 配置中的 library 作用是一樣的
		const remoteType =
			options.remoteType ||
			(options.library && isValidExternalsType(options.library.type)
				? /** @type {ExternalsType} */ (options.library.type)
				: "script");
		// 	enabledLibraryTypes 專門存儲 entry 需要輸出的 library 類型,然后被 EnableLibraryPlugin 插件消費,
		if (
			library &&
			!compiler.options.output.enabledLibraryTypes.includes(library.type)
		) {
			compiler.options.output.enabledLibraryTypes.push(library.type);
		}
		// 在完成所有內部插件注冊后處理 MF 插件
		compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => {
			if (
				options.exposes &&
				(Array.isArray(options.exposes)
					? options.exposes.length > 0
					: Object.keys(options.exposes).length > 0)
			) {
				// 如果有 expose 配置,則注冊一個 ContainerPlugin
				new ContainerPlugin({
					name: options.name,
					library,
					filename: options.filename,
					runtime: options.runtime,
					shareScope: options.shareScope,
					exposes: options.exposes
				}).apply(compiler);
			}
			if (
				options.remotes &&
				(Array.isArray(options.remotes)
					? options.remotes.length > 0
					: Object.keys(options.remotes).length > 0)
			) {
				// 如果有 expose 配置,則初始化一個 ContainerReferencePlugin
				new ContainerReferencePlugin({
					remoteType,
					shareScope: options.shareScope,
					remotes: options.remotes
				}).apply(compiler);
			}
			if (options.shared) {
				// 如果有 shared 配置,則初始化一個 SharePlugin
				new SharePlugin({
					shared: options.shared,
					shareScope: options.shareScope
				}).apply(compiler);
			}
		});
	}
}

從代碼中可以看出,MF 插件入口的代碼其實不復雜,核心的代碼不到 100 行,我們首先把焦點放在插件初始化的 options參數上,它的類型為 ModuleFederationPluginOptions。

這里有個細節(jié)可以注意下,因為 Webpack 的源碼是用純 JS 寫的,為了彌補如像 TypeScript 的類型注釋的優(yōu)勢使得源碼更加可讀的問題,Webpack 使用了 JSDoc 配合 VS Code,在大多數場景下也能起到類型注釋的效果,而 Webpack 根目錄下的 declarations目錄使用了 TS 定義了核心的一些數據類型,然后導出給其它 JS 文件在使用 JSDoc 時使用。

我們回到主題,我們看下ModuleFederationPluginOptions的類型定義:

export interface ModuleFederationPluginOptions {
	/**
	 * container 應用導出的模塊配置,一般是一個對象
	 */
	exposes?: Exposes;
	/**
	 * 打包產物的文件名稱
	 */
	filename?: string;
	/**
	 * 構建產物的類型,里面的 type 配置可以是 umd、commonjs、var 等類型
	 */
	library?: LibraryOptions;
	/**
	 * container 的名稱
	 */
	name?: string;
	/**
	 * 依賴的 remote 應用 library 類型,配置的值可以是 umd、commonjs、script、var 等
	 */
	remoteType?: ExternalsType;
	/**
	 * container 應用依賴的遠程應用
	 */
	remotes?: Remotes;
	/**
	 * 配置了該選項,會為模塊split 一個以該名稱命名的 chunk
	 */
	runtime?: EntryRuntime;
	/**
	 * 所有共享模塊的作用域名稱,默認為 default,很少會修改
	 */
	shareScope?: string;
	/**
	 * 應用之間需要共享的模塊
	 */
	shared?: Shared;
}

每個選項我都用注釋做了簡單的介紹,我們重點關注幾個常用的配置,對于 libraryruntime、

remoteType等配置平時很少使用,這里先不過多介紹,后面看到相關的源碼可以再回顧。

filenamename比較好理解,以上一小節(jié)的 app1 的 Webpack 配置為例,我們可以看到其配置如下:

   new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      // 省略其它配置
   })   

如果這樣配置,app1 正好expose了一些模塊給其它應用消費,例如 app2,則 app2 首先要通過 app1 的

name去找到它,也就是會在 remotes的配置中添加 app1 的指向,這里等會介紹 remotes選項時再細說。而 app2 在運行時就會加載到 app1 的構建產物 remoteEntry.js,訪問 app2 的服務,打開 network,我們可以看到其加載了 app1 的 remoteEntry.js

我們重點介紹下 exposes、remotesshared等選項。

在上面的 MF 插件源碼中,其核心的幾行代碼就是,在 afterPlugins hook觸發(fā)后(完成其它所有內置插件初始化后),根據是否有上面三個配置,來決定是否要注冊 ContainerPlugin、

ContainerReferencePlugin、SharePlugin等插件。所以更加核心的實現,是分別交給了上面三個插件去完成。

Exposes

exposes的配置是告訴 Webpack 當前應用導出給其它應用消費的模塊,首先我們來看下 exposes配置的類型定義:

export type Exposes = (ExposesItem | ExposesObject)[] | ExposesObject;
export type ExposesItem = string;
export type ExposesItems = ExposesItem[];
export interface ExposesObject {
	[k: string]: ExposesConfig | ExposesItem | ExposesItems;
}
export interface ExposesConfig {
	import: ExposesItem | ExposesItems;
	name?: string;
}

上面的類型定義相對來說比較簡單,只是套娃比較多,還是以上面 app1 的 Webpack 配置為例,據我平時了解到的,最常見的配置方式還是:

exposes: {
	'./input': './src/components/Input'
},

但是你也可以配置:

exposes: {
	'./input': {
    name: 'input',
    import: './src/components/Input'  
  }
},

這種方式配置會有什么不一樣了?這里會留一個懸念,在看后續(xù)的源碼中,我們再詳細介紹。

Remotes

remotes配置是告訴 Webpack 當前應用依賴了哪些遠程應用,我們來看下其類型定義:

export type Remotes = (RemotesItem | RemotesObject)[] | RemotesObject;
export type RemotesItem = string;
export type RemotesItems = RemotesItem[];
export interface RemotesObject {
	[k: string]: RemotesConfig | RemotesItem | RemotesItems;
}
export interface RemotesConfig {
	/**
	 * 共享模塊需要依賴的其它模塊
	 */
	external: RemotesItem | RemotesItems;
	// 共享作用域的名稱,默認為 default
	shareScope?: string;
}

還是以 app1 為例,我們回顧其 remote 的配置:

remotes: {
  app2: 'app2@http://localhost:3002/remoteEntry.js',
},

告訴了 Webpack 如果需要消費 app2 導出的模塊,那么則需要加載 app2 服務的 remoteEntry.js文件,所以 app1 在初始化的時候就會加載此文件,然后通過下面的方式加載 app2 導出的模塊:

import RemoteButton from 'app2/Button';

是不是有點神奇,這里面的實現用了什么黑魔法,簡單的幾個配置,然后啟動服務,就能消費其它遠程應用的模塊。保持耐心,后續(xù)我們將慢慢揭開其神秘的面紗。

Shared

MF 關于 shared配置部分是我個人覺得最復雜的部分,當然 SharedPlugin的實現也是相對來說比較復雜,因為這里牽扯到一些需要 shared配置延伸出的例如單例問題。 先留個懸念,稍后解釋單例問題,我們還是先看 shared配置類型定義:

export type Shared = (SharedItem | SharedObject)[] | SharedObject;
export type SharedItem = string;
export interface SharedObject {
	[k: string]: SharedConfig | SharedItem;
}
export interface SharedConfig {
  // 配置了 eager 是告訴 webpack 該模塊是作為一個 initial chunk,無論怎么樣,初始化都需要加載該模塊
	eager?: boolean;
	// 共享模塊依賴的模塊
	import?: false | SharedItem;
	// 共享模塊的包名
	packageName?: string;
  // 共享模塊的版本 
	requiredVersion?: false | string;
	// 如果配置了 key,查找共享模塊的時候,會在當前共享作用域查找配置的 key
	shareKey?: string;
	// 共享作用域
	shareScope?: string;
	// 是否需要保持單例
	singleton?: boolean;
	// 是否需要嚴格校驗共享模塊的版本,只有配置了 requiredVersion 配置該選型才有效
	strictVersion?: boolean;
  // 指定提供的模塊的版本,將會替代低版本的模塊,但是不會替代版本更好的模塊
	version?: false | string;
}

SharedConfig類型我們就可以看到 shared配置有很多的場景需要適配,每個配置我都做了簡單注釋來介紹。當然可能這個時候,不熟悉 MF 的小伙伴看到這些配置可能是懵逼的狀態(tài)。不用著急,這些配置項,在后面更加具體的源碼使用場景,我會再進行介紹,這里先留個印象。

我們還是看下 app1 的配置:

shared: {
	'react': {
   	 singleton: true, 
     requiredVersion: require('./package.json').dependencies.react
   },
   'react-dom': {
      singleton: true,
      requiredVersion: require('./package.json').dependencies['react-dom']
    },
    'lodash': {
      requiredVersion: require('./package.json').dependencies['lodash'],
      singleton: true,
   }
}

這里分別將 react、react-dom、lodash等三方包配置成了 shared,這樣有什么作用了?

實際上 shared配置是告訴 Webpack 這些依賴需要共享(復用) ,因為在 MF 的遠程模塊消費機制里面,多個應用之間可能會依賴相同的三方包,如果沒有一個共享機制,那么一定會導致多余的 chunk 加載,而且還有其它需要解決的問題。

以前面的 app1 和 app2 為例,app1 本身是一個 react 應用,它依賴了 app2 的一個組件,而 app2 同樣也依賴了 react ,那么如果沒有這個共享模塊的機制,那么 app1 消費 app2 的組件可能就還需要加載 app2 的構建的 react 依賴。而且我們知道,react 的運行機制是在同一個JS runtime 里面,是不能同時存在兩個 react 實例的,這也是

singleton配置的由來,它的作用就是為了解決類似這樣的場景。

當然,要講清楚這部分的原理,還有運行機制,除了需要一定的 MF 使用經驗外,還需要對其源碼有一定的了解,我們后續(xù)在剖析 SharedPlugin插件源碼時再詳細聊。

小結

雖然 MF 插件入口的源碼部分相對來說還是不復雜的,所以本小節(jié)我們聚焦在其配置上。實際上對于 上面提到的一些配置,例如 MF 插件的 libraryremoteType等配置在官網是沒有提到的,包括 exposes、

remotes、shared等配置的一些更加高級的選項,這也是 Webpack 配置復雜然后官網又不完全介紹一直被人詬病的地方。

總結

本文我們從 MF 插件主入口出發(fā),分析了其插件的注冊時機,并且通過閱讀這部分的源碼,我們了解到:

  • 插件的配置選項除了常用的 exposes、remotes、shared、filename、name 等之外還有

library、remoteTypesharedScope等配置項,可以指定 exposesremotes模塊的

library類型;

  • MF 核心的源碼實現是通過其它三個插件 ContainerPlugin

ContainerReferencePlugin、SharePlugin 等來實現,然后根據是否傳入 exposes、

remotes、shared來決定是否需要初始化各個插件;

  • exposes、remotesshared等選項有很多進階的配置,特別是 shared配置比較復雜,從共享三方依賴、單例、版本鎖定等角度思考,就可以想象這里面的設計不簡單。

后續(xù)文章

下一篇文章,我們開始逐漸進入深水區(qū),首先深入到 CotainerPlugin的源碼,一步步揭開其神秘的面紗。為了更好理解后續(xù)的文章,建議讀者了解一下 Webpack 構建流程,特別是核心的構建階段,DependencyModule之間的轉換流程,更多關于微前端架構ModuleFederationPlugin的資料請關注腳本之家其它相關文章!

相關文章

  • Android中WebView用法實例分析

    Android中WebView用法實例分析

    這篇文章主要介紹了Android中WebView用法,以實例形式較為詳細的分析了Android中WebView的功能、注意事項與使用技巧,非常具有實用價值,需要的朋友可以參考下
    2015-10-10
  • Android開發(fā)中如何解決Fragment +Viewpager滑動頁面重復加載的問題

    Android開發(fā)中如何解決Fragment +Viewpager滑動頁面重復加載的問題

    這篇文章主要介紹了Android開發(fā)中如何解決Fragment +Viewpager滑動頁面重復加載的問題 ,需要的朋友可以參考下
    2017-07-07
  • Android14(U)適配攻略

    Android14(U)適配攻略

    本文主要介紹了Android14(U)適配攻略,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-06-06
  • Android應用?;顚嵺`詳解

    Android應用?;顚嵺`詳解

    這篇文章主要介紹了Android應用?;顚嵺`詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • Android Retrofit 2.0框架上傳圖片解決方案

    Android Retrofit 2.0框架上傳圖片解決方案

    這篇文章主要介紹了Android Retrofit 2.0框架上傳一張與多張圖片解決方案,感興趣的小伙伴們可以參考一下
    2016-03-03
  • Android編程開發(fā)之打開文件的Intent及使用方法

    Android編程開發(fā)之打開文件的Intent及使用方法

    這篇文章主要介紹了Android編程開發(fā)之打開文件的Intent及使用方法,已實例形式分析了Android打開文件Intent的相關布局及功能實現技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-10-10
  • 解析后臺進程對Android性能影響的詳解

    解析后臺進程對Android性能影響的詳解

    本篇文章是對Android中后臺進程對Android性能的影響進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • android 跳轉進市場的實現代碼

    android 跳轉進市場的實現代碼

    本篇文章是對android中跳轉進市場的實現代碼進行了詳細的分析介紹,需要的朋友參考下
    2013-06-06
  • Android仿微信通訊錄打造帶懸停頭部的分組列表(上)

    Android仿微信通訊錄打造帶懸停頭部的分組列表(上)

    這篇文章主要介紹了Android仿微信通訊錄導航分組列表,使用ItemDecoration為RecyclerView打造帶懸停頭部的分組列表,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Android中實現多線程操作的幾種方式

    Android中實現多線程操作的幾種方式

    多線程一直是一個老大難的問題,首先因為它難以理解,其次在實際工作中我們需要面對的關于線程安全問題也并不常見,今天就來總結一下實現多線程的幾種方式,感興趣的可以了解一下
    2021-06-06

最新評論