Java、Kotlin、Go中線程與協(xié)程的區(qū)別-創(chuàng)新互聯(lián)

這篇文章給大家介紹Java、Kotlin、Go中線程與協(xié)程的區(qū)別,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

創(chuàng)新互聯(lián)建站從2013年開始,是專業(yè)互聯(lián)網技術服務公司,擁有項目成都網站設計、成都網站制作網站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元化隆做網站,已為上家服務,為化隆各地企業(yè)和個人服務,聯(lián)系電話:18980820575

協(xié)程是什么


協(xié)程并不是 Go 提出來的新概念,其他的一些編程語言,例如:Go、Python 等都可以在語言層面上實現(xiàn)協(xié)程,甚至是 Java,也可以通過使用擴展庫來間接地支持協(xié)程。

當在網上搜索協(xié)程時,我們會看到:

  • Kotlin 官方文檔說「本質上,協(xié)程是輕量級的線程」。

  • 很多博客提到「不需要從用戶態(tài)切換到內核態(tài)」、「是協(xié)作式的」等等。

「協(xié)程 Coroutines」源自 Simula 和 Modula-2 語言,這個術語早在 1958 年就被 Melvin Edward Conway 發(fā)明并用于構建匯編程序,說明協(xié)程是一種編程思想,并不局限于特定的語言。

協(xié)程的好處

性能比 Java 好很多,甚至代碼實現(xiàn)都比 Java 要簡潔很多。

進程

進程是什么

計算機的核心是 CPU,執(zhí)行所有的計算任務;操作系統(tǒng)負責任務的調度、資源的分配和管理;應用程序是具有某種功能的程序,程序是運行在操作系統(tǒng)上的。

進程是一個具有一定獨立功能的程序在一個數(shù)據(jù)集上的一次動態(tài)執(zhí)行的過程,是操作系統(tǒng)進行資源分配和調度的一個獨立單位,是應用程序運行的載體。

進程組成

進程由三部分組成:

  • 程序:描述進程要完成的功能,是控制進程執(zhí)行的指令集。

  • 數(shù)據(jù)集合:程序在執(zhí)行時所需要的數(shù)據(jù)和工作區(qū)。

  • 進程控制塊:(Program Control Block,簡稱PCB),包含進程的描述信息和控制信息,是進程存在的標志。

進程特征

  • 動態(tài)性:進程是程序的一次執(zhí)行過程,是臨時的,有生命期的,是動態(tài)產生,動態(tài)消亡的。

  • 并發(fā)性:任何進程都可以同其他進程一起并發(fā)執(zhí)行。

  • 獨立性:進程是系統(tǒng)進行資源分配和調度的一個獨立單位。結構性:進程由程序、數(shù)據(jù)和進程控制塊三部分組成。

線程

線程是什么

線程是程序執(zhí)行中一個單一的順序控制流程,是程序執(zhí)行流的最小單元,是處理器調度和分派的基本單位。一個進程可以有一個或多個線程,各個線程之間共享程序的內存空間(也就是所在進程的內存空間)。

線程組成線程ID、當前指令指針(PC)寄存器堆棧

任務調度

大部分操作系統(tǒng)(如Windows、Linux)的任務調度是采用時間片輪轉的搶占式調度方式。

在一個進程中,當一個線程任務執(zhí)行幾毫秒后,會由操作系統(tǒng)的內核(負責管理各個任務)進行調度,通過硬件的計數(shù)器中斷處理器,讓該線程強制暫停并將該線程的寄存器放入內存中,通過查看線程列表決定接下來執(zhí)行哪一個線程,并從內存中恢復該線程的寄存器,最后恢復該線程的執(zhí)行,從而去執(zhí)行下一個任務。

進程與線程的區(qū)別

線程是程序執(zhí)行的最小單位,而進程是操作系統(tǒng)分配資源的最小單位;一個進程由一個或多個線程組成,線程是一個進程中代碼的不同執(zhí)行路線;進程之間相互獨立,但同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數(shù)據(jù)集、堆等)及一些進程級的資源(如打開文件和信號),某進程內的線程在其它進程不可見;調度和切換:線程上下文切換進程上下文切換得多。

Java、Kotlin、Go中線程與協(xié)程的區(qū)別

線程的實現(xiàn)模型

程序一般不會直接去使用內核線程,而是去使用內核線程的一種高級接口——輕量級進程(Lightweight Process,LWP),輕量級進程就是我們通常意義上所講的線程,也被叫做用戶線程。

一對一模型

一個用戶線程對應一個內核線程,如果是多核的 CPU,那么線程之間是真正的并發(fā)。

缺點:

  • 內核線程的數(shù)量有限,一對一模型使用的用戶線程數(shù)量有限制。

  • 內核線程的調度,上下文切換的開銷較大(雖然沒有進程上下文切換的開銷大),導致用戶線程的執(zhí)行效率下降。

多對一模型

多個用戶線程映射到一個內核線程上,線程間的切換由用戶態(tài)的代碼來進行。用戶線程的建立、同步、銷毀都是在用戶態(tài)中完成,不需要內核的介入。因此多對一的上下文切換速度快很多,且用戶線程的數(shù)量幾乎沒有限制。

缺點:

  • 若一個用戶線程阻塞,其他所有線程都無法執(zhí)行,此時內核線程處于阻塞狀態(tài)。

  • 處理器數(shù)量的增加,不會對多對一模型的線程性能造成影響,因為所有的用戶線程都映射到了一個處理器上。

多對多模型

結合了一對一模型多對一模型的優(yōu)點,多個用戶線程映射到多個內核線程上,由線程庫負責在可用的可調度實體上調度用戶線程。這樣線程間的上下文切換很快,因為它避免了系統(tǒng)調用。但是增加了系統(tǒng)的復雜性。

優(yōu)點:

一個用戶線程的阻塞不會導致所有線程的阻塞,因為此時還有別的內核線程被調度來執(zhí)行;多對多模型對用戶線程的數(shù)量沒有限制;在多處理器的操作系統(tǒng)中,多對多模型的線程也能得到一定的性能提升,但提升的幅度不如一對一模型的高。

線程的“并發(fā)”

只有在線程的數(shù)量 < 處理器的數(shù)量時,線程的并發(fā)才是真正的并發(fā),這時不同的線程運行在不同的處理器上。但是當線程的數(shù)量 > 處理器的數(shù)量時,會出現(xiàn)一個處理器運行多個線程的情況。

在單個處理器運行多個線程時,并發(fā)是一種模擬出來的狀態(tài)。操作系統(tǒng)采用時間片輪轉的方式輪流執(zhí)行每一個線程?,F(xiàn)在,幾乎所有的現(xiàn)代操作系統(tǒng)采用的都是時間片輪轉的搶占式調度方式。

協(xié)程

當在網上搜索協(xié)程時,我們會看到:

本質上,協(xié)程是輕量級的線程。很多博客提到「不需要從用戶態(tài)切換到內核態(tài)」、「是協(xié)作式的」。

協(xié)程也并不是 Go 提出來的,協(xié)程是一種編程思想,并不局限于特定的語言。Go、Python、Kotlin 都可以在語言層面上實現(xiàn)協(xié)程,Java 也可以通過擴展庫的方式間接支持協(xié)程。

協(xié)程比線程更加輕量級,可以由程序員自己管理的輕量級線程,對內核不可見。

協(xié)程的目的

在傳統(tǒng)的 J2EE 系統(tǒng)中都是基于每個請求占用一個線程去完成完整的業(yè)務邏輯(包括事務)。所以系統(tǒng)的吞吐能力取決于每個線程的操作耗時。如果遇到很耗時的 I/O 行為,則整個系統(tǒng)的吞吐立刻下降,因為這個時候線程一直處于阻塞狀態(tài),如果線程很多的時候,會存在很多線程處于空閑狀態(tài)(等待該線程執(zhí)行完才能執(zhí)行),造成了資源應用不徹底。

最常見的例子就是 JDBC(它是同步阻塞的),這也是為什么很多人都說數(shù)據(jù)庫是瓶頸的原因。這里的耗時其實是讓 CPU 一直在等待 I/O 返回,說白了線程根本沒有利用 CPU 去做運算,而是處于空轉狀態(tài)。而另外過多的線程,也會帶來更多的 ContextSwitch 開銷。

對于上述問題,現(xiàn)階段行業(yè)里的比較流行的解決方案之一就是單線程加上異步回調。其代表派是 node.js 以及 Java 里的新秀 Vert.x。

而協(xié)程的目的就是當出現(xiàn)長時間的 I/O 操作時,通過讓出目前的協(xié)程調度,執(zhí)行下一個任務的方式,來消除 ContextSwitch 上的開銷。

協(xié)程的特點線程的切換由操作系統(tǒng)負責調度,協(xié)程由用戶自己進行調度,減少了上下文切換,提高了效率線程的默認 Stack 是1M,協(xié)程更加輕量,是 1K,在相同內存中可以開啟更多的協(xié)程。由于在同一個線程上,因此可以避免競爭關系而使用鎖。適用于被阻塞的,且需要大量并發(fā)的場景。但不適用于大量計算的多線程,遇到此種情況,更好用線程去解決。

協(xié)程的原理

當出現(xiàn)IO阻塞的時候,由協(xié)程的調度器進行調度,通過將數(shù)據(jù)流立刻yield掉(主動讓出),并且記錄當前棧上的數(shù)據(jù),阻塞完后立刻再通過線程恢復棧,并把阻塞的結果放到這個線程上去跑,這樣看上去好像跟寫同步代碼沒有任何差別,這整個流程可以稱為coroutine,而跑在由coroutine負責調度的線程稱為Fiber。比如Golang里的 go關鍵字其實就是負責開啟一個Fiber,讓func邏輯跑在上面。

由于協(xié)程的暫停完全由程序控制,發(fā)生在用戶態(tài)上;而線程的阻塞狀態(tài)是由操作系統(tǒng)內核來進行切換,發(fā)生在內核態(tài)上。
因此,協(xié)程的開銷遠遠小于線程的開銷,也就沒有了 ContextSwitch 上的開銷。

假設程序中默認創(chuàng)建兩個線程為協(xié)程使用,在主線程中創(chuàng)建協(xié)程ABCD…,分別存儲在就緒隊列中,調度器首先會分配一個工作線程A執(zhí)行協(xié)程A,另外一個工作線程B執(zhí)行協(xié)程B,其它創(chuàng)建的協(xié)程將會放在隊列中進行排隊等待。

Java、Kotlin、Go中線程與協(xié)程的區(qū)別

當協(xié)程A調用暫停方法或被阻塞時,協(xié)程A會進入到掛起隊列,調度器會調用等待隊列中的其它協(xié)程搶占線程A執(zhí)行。當協(xié)程A被喚醒時,它需要重新進入到就緒隊列中,通過調度器搶占線程,如果搶占成功,就繼續(xù)執(zhí)行協(xié)程A,失敗則繼續(xù)等待搶占線程。

Java、Kotlin、Go中線程與協(xié)程的區(qū)別

Java、Kotlin、Go 的線程與協(xié)程

Java 在 Linux 操作系統(tǒng)下使用的是用戶線程+輕量級線程,一個用戶線程映射到一個內核線程,線程之間的切換就涉及到了上下文切換。所以在 Java 中并不適合創(chuàng)建大量的線程,否則效率會很低。可以先看下 Kotlin 和 Go 的協(xié)程:

Kotlin 的協(xié)程

Kotlin 在誕生之初,目標就是完全兼容 Java,卻是一門非常務實的語言,其中一個特性,就是支持協(xié)程。

但是 Kotlin 最終還是運行在 JVM 中的,目前的 JVM 并不支持協(xié)程,Kotlin 作為一門編程語言,也只是能在語言層面支持協(xié)程。Kotlin 的協(xié)程是用于異步編程等場景的,在語言級提供協(xié)程支持,而將大部分功能委托給庫。

使用「線程」的代碼

@Test
fun testThread() {
 // 執(zhí)行時間 1min+
 val c = AtomicLong()
 for (i in 1..1_000_000L)
 thread(start = true) {
  c.addAndGet(i)
 }
 println(c.get())
}

上述代碼創(chuàng)建了100 萬個線程,在每個線程里僅僅調用了 add 操作,但是由于創(chuàng)建線程太多,這個測試用例在我的機器上要跑 1 分鐘左右。

使用「協(xié)程」的代碼

@Test
fun testLaunch() {
 val c = AtomicLong()
 runBlocking {
 for (i in 1..1_000_000L)
  launch {
  c.addAndGet(workload(i))
  }
 }
 print(c.get())
}

suspend fun workload(n: Long): Long {
 delay(1000)
 return n

這段代碼是創(chuàng)建了100 萬個協(xié)程,測試用例在我的機器上執(zhí)行時間大概是 10 秒鐘。而且這段代碼的每個協(xié)程都 delay 了 1 秒鐘,執(zhí)行效率仍然遠遠高于線程。

詳細的語法可以查看 Kotlin 的官方網站:https://www.kotlincn.net/docs/reference/coroutines/basics.html

其中關鍵字launch 是開啟了一個協(xié)程,關鍵字suspend 是掛起一個協(xié)程,而不會阻塞。現(xiàn)在在看這個流程,應該就懂了~

Java、Kotlin、Go中線程與協(xié)程的區(qū)別

Go 的協(xié)程

官方例程:https://gobyexample-cn.github.io/goroutines

go語言層面并不支持多進程或多線程,但是協(xié)程更好用,協(xié)程被稱為用戶態(tài)線程,不存在CPU上下文切換問題,效率非常高。下面是一個簡單的協(xié)程演示代碼:

package main

func main() {
 go say("Hello World")
}

func say(s string) {
 println(s)
}

Java 的 Kilim 協(xié)程框架

目前 Java 原生語言暫時不支持協(xié)程,可以使用 kilim,具體原理可以看官方文檔,暫時還沒有研究~

Java 的 Project Loom

Java 也在逐步支持協(xié)程,其項目就是Project Loom(https://openjdk.java.net/projects/loom/)。這個項目在18年底的時候已經達到可初步演示的原型階段。不同于之前的方案,Project Loom 是從 JVM 層面對多線程技術進行徹底的改變。

官方介紹:
http://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html

其中一段介紹了為什么引入這個項目:

One of Java's most important contributions when it was first released, over twenty years ago, was the easy access to threads and synchronization primitives. Java threads (either used directly, or indirectly through, for example, Java servlets processing HTTP requests) provided a relatively simple abstraction for writing concurrent applications. These days, however, one of the main difficulties in writing concurrent programs that meet today's requirements is that the software unit of concurrency offered by the runtime — the thread — cannot match the scale of the domain's unit of concurrency, be it a user, a transaction or even a single operation. Even if the unit of application concurrency is coarse — say, a session, represented by single socket connection — a server can handle upward of a million concurrent open sockets, yet the Java runtime, which uses the operating system's threads for its implementation of Java threads, cannot efficiently handle more than a few thousand. A mismatch in several orders of magnitude has a big impact.

文章大意就是本文上面所說的,Java 的用戶線程與內核線程是一對一的關系,一個 Java 進程很難創(chuàng)建上千個線程,如果是對于 I/O 阻塞的程序(例如數(shù)據(jù)庫讀取/Web服務),性能會很低下,所以要采用類似于協(xié)程的機制。

使用 Fiber

在引入 Project Loom 之后,JDK 將引入一個新類:java.lang.Fiber。此類與 java.lang.Thread 一起,都成為了 java.lang.Strand 的子類。即線程變成了一個虛擬的概念,有兩種實現(xiàn)方法:Fiber 所表示的輕量線程和 Thread 所表示的傳統(tǒng)的重量級線程。

Fiber f = Fiber.schedule(() -> {
 println("Hello 1");
 lock.lock(); // 等待鎖不會掛起線程
 try {
 println("Hello 2");
 } finally {
 lock.unlock();
 }
 println("Hello 3");
})

只需執(zhí)行Fiber.schedule(Runnable task) 就能在Fiber 中執(zhí)行任務。最重要的是,上面例子中的 lock.lock() 操作將不再掛起底層線程。除了Lock 不再掛起線程以外,像Socket BIO 操作也不再掛起線程。 但 synchronized,以及 Native 方法中線程掛起操作無法避免。

關于Java、Kotlin、Go中線程與協(xié)程的區(qū)別就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

當前文章:Java、Kotlin、Go中線程與協(xié)程的區(qū)別-創(chuàng)新互聯(lián)
鏈接URL:http://bm7419.com/article34/hdhse.html

成都網站建設公司_創(chuàng)新互聯(lián),為您提供用戶體驗網站設計公司、網站設計軟件開發(fā)、App開發(fā)、做網站

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)