|
前言Go 語(yǔ)言比 Java 語(yǔ)言性能優(yōu)越的一個(gè)原因,就是輕量級(jí)線程 協(xié)程是什么協(xié)程并不是 Go 提出來的新概念,其他的一些編程語(yǔ)言,例如:Go、Python 等都可以在語(yǔ)言層面上實(shí)現(xiàn)協(xié)程,甚至是 Java,也可以通過使用擴(kuò)展庫(kù)來間接地支持協(xié)程。 當(dāng)在網(wǎng)上搜索協(xié)程時(shí),我們會(huì)看到:
「協(xié)程 Coroutines」源自 Simula 和 Modula-2 語(yǔ)言,這個(gè)術(shù)語(yǔ)早在 1958 年就被 Melvin Edward Conway 發(fā)明并用于構(gòu)建匯編程序,說明協(xié)程是一種編程思想,并不局限于特定的語(yǔ)言。 協(xié)程的好處性能比 Java 好很多,甚至代碼實(shí)現(xiàn)都比 Java 要簡(jiǎn)潔很多。 那這究竟又是為什么呢?下面一一分析。 說明:下面關(guān)于進(jìn)程和線程的部分,幾乎完全參考自:https://www.cnblogs.com/Survivalist/p/11527949.html,這篇文章寫得太好了~~~ 進(jìn)程進(jìn)程是什么計(jì)算機(jī)的核心是 CPU,執(zhí)行所有的計(jì)算任務(wù);操作系統(tǒng)負(fù)責(zé)任務(wù)的調(diào)度、資源的分配和管理;應(yīng)用程序是具有某種功能的程序,程序是運(yùn)行在操作系統(tǒng)上的。 進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序在一個(gè)數(shù)據(jù)集上的一次動(dòng)態(tài)執(zhí)行的過程,是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,是應(yīng)用程序運(yùn)行的載體。 進(jìn)程組成進(jìn)程由三部分組成:
進(jìn)程特征
線程線程是什么線程是程序執(zhí)行中一個(gè)單一的 線程組成
任務(wù)調(diào)度大部分操作系統(tǒng)(如Windows、Linux)的任務(wù)調(diào)度是采用 在一個(gè)進(jìn)程中,當(dāng)一個(gè)線程任務(wù)執(zhí)行幾毫秒后,會(huì)由操作系統(tǒng)的內(nèi)核(負(fù)責(zé)管理各個(gè)任務(wù))進(jìn)行調(diào)度,通過硬件的計(jì)數(shù)器中斷處理器,讓該線程強(qiáng)制暫停并將該線程的寄存器放入內(nèi)存中,通過查看線程列表決定接下來執(zhí)行哪一個(gè)線程,并從內(nèi)存中恢復(fù)該線程的寄存器,最后恢復(fù)該線程的執(zhí)行,從而去執(zhí)行下一個(gè)任務(wù)。 進(jìn)程與線程的區(qū)別
線程的實(shí)現(xiàn)模型程序一般不會(huì)直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級(jí)接口—— 一對(duì)一模型一個(gè)用戶線程對(duì)應(yīng)一個(gè)內(nèi)核線程,如果是多核的 CPU,那么線程之間是真正的并發(fā)。 缺點(diǎn):
多對(duì)一模型
缺點(diǎn):
多對(duì)多模型結(jié)合了 優(yōu)點(diǎn):
線程的“并發(fā)”只有在線程的數(shù)量 < 處理器的數(shù)量時(shí),線程的并發(fā)才是真正的并發(fā),這時(shí)不同的線程運(yùn)行在不同的處理器上。但是當(dāng)線程的數(shù)量 > 處理器的數(shù)量時(shí),會(huì)出現(xiàn)一個(gè)處理器運(yùn)行多個(gè)線程的情況。 在單個(gè)處理器運(yùn)行多個(gè)線程時(shí),并發(fā)是一種模擬出來的狀態(tài)。操作系統(tǒng)采用時(shí)間片輪轉(zhuǎn)的方式輪流執(zhí)行每一個(gè)線程?,F(xiàn)在,幾乎所有的現(xiàn)代操作系統(tǒng)采用的都是時(shí)間片輪轉(zhuǎn)的搶占式調(diào)度方式。 協(xié)程當(dāng)在網(wǎng)上搜索協(xié)程時(shí),我們會(huì)看到:
協(xié)程也并不是 Go 提出來的,協(xié)程是一種編程思想,并不局限于特定的語(yǔ)言。Go、Python、Kotlin 都可以在語(yǔ)言層面上實(shí)現(xiàn)協(xié)程,Java 也可以通過擴(kuò)展庫(kù)的方式間接支持協(xié)程。 協(xié)程比線程更加輕量級(jí),可以由程序員自己管理的輕量級(jí)線程,對(duì)內(nèi)核不可見。 協(xié)程的目的在傳統(tǒng)的 J2EE 系統(tǒng)中都是基于每個(gè)請(qǐng)求占用一個(gè)線程去完成完整的業(yè)務(wù)邏輯(包括事務(wù))。所以系統(tǒng)的吞吐能力取決于每個(gè)線程的操作耗時(shí)。如果遇到很耗時(shí)的 I/O 行為,則整個(gè)系統(tǒng)的吞吐立刻下降,因?yàn)檫@個(gè)時(shí)候線程一直處于阻塞狀態(tài),如果線程很多的時(shí)候,會(huì)存在很多線程處于空閑狀態(tài)(等待該線程執(zhí)行完才能執(zhí)行),造成了資源應(yīng)用不徹底。 最常見的例子就是 JDBC(它是同步阻塞的),這也是為什么很多人都說數(shù)據(jù)庫(kù)是瓶頸的原因。這里的耗時(shí)其實(shí)是讓 CPU 一直在等待 I/O 返回,說白了線程根本沒有利用 CPU 去做運(yùn)算,而是處于空轉(zhuǎn)狀態(tài)。而另外過多的線程,也會(huì)帶來更多的 ContextSwitch 開銷。 對(duì)于上述問題,現(xiàn)階段行業(yè)里的比較流行的解決方案之一就是單線程加上異步回調(diào)。其代表派是 node.js 以及 Java 里的新秀 Vert.x。 而協(xié)程的目的就是當(dāng)出現(xiàn)長(zhǎng)時(shí)間的 I/O 操作時(shí),通過讓出目前的協(xié)程調(diào)度,執(zhí)行下一個(gè)任務(wù)的方式,來消除 ContextSwitch 上的開銷。 協(xié)程的特點(diǎn)
協(xié)程的原理當(dāng)出現(xiàn)IO阻塞的時(shí)候,由協(xié)程的調(diào)度器進(jìn)行調(diào)度,通過將數(shù)據(jù)流立刻yield掉(主動(dòng)讓出),并且記錄當(dāng)前棧上的數(shù)據(jù),阻塞完后立刻再通過線程恢復(fù)棧,并把阻塞的結(jié)果放到這個(gè)線程上去跑,這樣看上去好像跟寫同步代碼沒有任何差別,這整個(gè)流程可以稱為 由于協(xié)程的暫停完全由程序控制,發(fā)生在用戶態(tài)上;而線程的阻塞狀態(tài)是由操作系統(tǒng)內(nèi)核來進(jìn)行切換,發(fā)生在內(nèi)核態(tài)上。 假設(shè)程序中默認(rèn)創(chuàng)建兩個(gè)線程為協(xié)程使用,在主線程中創(chuàng)建協(xié)程ABCD…,分別存儲(chǔ)在就緒隊(duì)列中,調(diào)度器首先會(huì)分配一個(gè)工作線程A執(zhí)行協(xié)程A,另外一個(gè)工作線程B執(zhí)行協(xié)程B,其它創(chuàng)建的協(xié)程將會(huì)放在隊(duì)列中進(jìn)行排隊(duì)等待。
當(dāng)協(xié)程A調(diào)用暫停方法或被阻塞時(shí),協(xié)程A會(huì)進(jìn)入到掛起隊(duì)列,調(diào)度器會(huì)調(diào)用等待隊(duì)列中的其它協(xié)程搶占線程A執(zhí)行。當(dāng)協(xié)程A被喚醒時(shí),它需要重新進(jìn)入到就緒隊(duì)列中,通過調(diào)度器搶占線程,如果搶占成功,就繼續(xù)執(zhí)行協(xié)程A,失敗則繼續(xù)等待搶占線程。
Java、Kotlin、Go 的線程與協(xié)程Java 在 Linux 操作系統(tǒng)下使用的是用戶線程+輕量級(jí)線程, Kotlin 的協(xié)程Kotlin 在誕生之初,目標(biāo)就是完全兼容 Java,卻是一門非常務(wù)實(shí)的語(yǔ)言,其中一個(gè)特性,就是支持協(xié)程。 但是 Kotlin 最終還是運(yùn)行在 JVM 中的,目前的 JVM 并不支持協(xié)程,Kotlin 作為一門編程語(yǔ)言,也只是能在語(yǔ)言層面支持協(xié)程。Kotlin 的協(xié)程是用于異步編程等場(chǎng)景的,在語(yǔ)言級(jí)提供協(xié)程支持,而將大部分功能委托給庫(kù)。 使用「線程」的代碼
上述代碼創(chuàng)建了 使用「協(xié)程」的代碼
這段代碼是創(chuàng)建了 詳細(xì)的語(yǔ)法可以查看 Kotlin 的官方網(wǎng)站:https://www./docs/reference/coroutines/basics.html 其中關(guān)鍵字
Go 的協(xié)程官方例程:https://gobyexample-cn./goroutines go語(yǔ)言層面并
Java 的 Kilim 協(xié)程框架目前 Java 原生語(yǔ)言暫時(shí)不支持協(xié)程,可以使用 kilim,具體原理可以看官方文檔,暫時(shí)還沒有研究~ Java 的 Project LoomJava 也在逐步支持協(xié)程,其項(xiàng)目就是 官方介紹: 其中一段介紹了為什么引入這個(gè)項(xiàng)目:
文章大意就是本文上面所說的,Java 的用戶線程與內(nèi)核線程是一對(duì)一的關(guān)系,一個(gè) Java 進(jìn)程很難創(chuàng)建上千個(gè)線程,如果是對(duì)于 I/O 阻塞的程序(例如數(shù)據(jù)庫(kù)讀取/Web服務(wù)),性能會(huì)很低下,所以要采用類似于協(xié)程的機(jī)制。 使用 Fiber在引入 Project Loom 之后,JDK 將引入一個(gè)新類:java.lang.Fiber。此類與 java.lang.Thread 一起,都成為了 java.lang.Strand 的子類。即線程變成了一個(gè)虛擬的概念,有兩種實(shí)現(xiàn)方法:Fiber 所表示的輕量線程和 Thread 所表示的傳統(tǒng)的重量級(jí)線程。
只需執(zhí)行 總結(jié)協(xié)程大法好,比線程更輕量級(jí),但是僅針對(duì) I/O 阻塞才有效;對(duì)于 CPU 密集型的應(yīng)用,因?yàn)?CPU 一直都在計(jì)算并沒有什么空閑,所以沒有什么作用。 Kotlin 兼容 Java,在編譯器、語(yǔ)言層面實(shí)現(xiàn)了協(xié)程,JVM 底層并不支持協(xié)程;Go 天生就是支持協(xié)程的,不支持多進(jìn)程和多線程。Java 的 參考資料
公眾號(hào)coding 筆記、點(diǎn)滴記錄,以后的文章也會(huì)同步到公眾號(hào)(Coding Insight)中,希望大家關(guān)注_ 代碼和思維導(dǎo)圖在 GitHub 項(xiàng)目中,歡迎大家 star!
|
|
|