Lua協程

目錄

1.什麼是協程

1.1 進程和線程

1.2 協程

1.2.1 協程掛起與喚醒

1.3 進程、線程和協程的區別

2.Lua協程


協程是追求極限性能和優美的代碼結構的產物,協程允許我們寫同步代碼的邏輯,卻做着異步的時,避免了回調嵌套,使得代碼邏輯清晰。

1.什麼是協程

1.1 進程和線程

在瞭解什麼是協程之前,我們先要了解什麼是進程和線程。

  1. 進程是應用程序的啓動實例,比如我們打開一個軟件,運行一個遊戲,就開啓了一個進程。進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。每個進程都有自己的獨立內存空間,不同進程通過進程間通信來通信。由於進程比較重量,佔據獨立的內存,所以上下文進程間的切換開銷(棧、寄存器、虛擬內存、文件句柄等)比較大,但相對比較穩定安全。
  2. 線程從屬於進程,是程序的實際執行者。一個進程至少包含一個主線程,也可以有更多的子線程。線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。

線程的狀態轉換關係:

但是線程不同狀態之間的轉化是誰來負責實現的呢?是JVM嗎?並不是,JVM需要通過操作系統內核中的TCB(Thread Control Block)模塊來改變線程的狀態,這一過程需要耗費一定的CPU資源。

進程和線程存在以下的痛點:

  • 涉及到同步鎖
  • 線程在不同狀態之間的轉換
  • 線程上下文的切換

以上涉及到任何一點,都是非常耗費性能的操作。

1.2 協程

協程(Coroutines)是一種比線程更加輕量級的存在。正如一個進程可以擁有多個線程一樣,一個線程也可以擁有多個協程。協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。

1.2.1 協程掛起與喚醒

協程是爲了使用異步的優勢,異步操作是爲了避免IO操作阻塞線程。那麼協程掛起的時刻應該是當前協程發起異步操作的時候,而喚醒應該在其他協程退出,並且它的異步操作完成時。爲了保證喚醒時能正常運行,需要正確保存並恢復其運行時的上下文。

具體操作步驟爲:

  • 保存當前協程的上下文:運行棧、返回地址、寄存器狀態
  • 設置將要喚醒的協程的入口指令地址到IP(指令)寄存器
  • 恢復將要喚醒的協程的上下文

1.3 進程、線程和協程的區別

上下文切換

  • 進程:包括頁目錄以使用新的地址空間;切換內核棧和硬件上下文
  • 線程:線程和進程的最大區別就在於地址空間,所以線程切換只需要切換內核棧和硬件上下文
  • 協程又稱爲輕量級線程,每個協程都自帶了一個棧,可以認爲一個協程就是一個函數和這個存放這個函數運行時數據的棧,這個棧非常小,一般只有幾十kb。

進程,線程,協程三者之間的區別。

 

進程

線程

協程

切換者

操作系統

操作系統

用戶

切換時機

根據操作系統自己的切換策略,用戶無法感知

根據操作系統自己的切換策略,用戶無法感知

用戶

切換內容

頁全局目錄

內核棧

硬件上下文

內核棧

硬件上下文

硬件上下文

切換內容的保存

保存於內核棧中

保存於內核棧中

 

保存於用戶自己的變量(用戶棧或者堆)

切換過程

用戶態->內核態->用戶態

用戶態->內核態->用戶態

用戶態(不陷入內核態)

切換效率

2.Lua協程

Lua協程的基本語法如下:

方法

描述

coroutine.create() 創建 coroutine,返回 coroutine, 參數是一個函數,當和 resume 配合使用的時候就喚醒函數調用
coroutine.resume() 重啓 coroutine,和 create 配合使用
coroutine.yield() 掛起 coroutine,將 coroutine 設置爲掛起狀態,這個和 resume 配合使用能有很多有用的效果
coroutine.status() 查看 coroutine 的狀態
注:coroutine 的狀態有三種:dead,suspended,running,具體什麼時候有這樣的狀態請參考下面的程序
coroutine.wrap() 創建 coroutine,返回一個函數,一旦你調用這個函數,就進入 coroutine,和 create 功能重複
coroutine.running() 返回正在跑的 coroutine,一個 coroutine 就是一個線程,當使用running的時候,就是返回一個 corouting 的線程號

我們使用Lua的協程來完成生產者-消費者這一經典問題,實現代碼如下:

local newProductor
 
function productor()
     local i = 0
     while true do
          i = i + 1
          send(i)     -- 將生產的物品發送給消費者
     end
end
 
function consumer()
     while true do
          local i = receive()     -- 從生產者那裏得到物品
          print(i)
     end
end
 
function receive()
     local status, value = coroutine.resume(newProductor)
     return value
end
 
function send(x)
     coroutine.yield(x)     -- x表示需要發送的值,值返回以後,就掛起該協同程序
end
 
-- 啓動程序
newProductor = coroutine.create(productor)
consumer()

輸出結果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
……

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章