💢歡迎來到張胤塵的技術站
💥技術如江河,匯聚眾志成。代碼似星辰,照亮行征程。開源精神長,傳承永不忘。攜手共前行,未來更輝煌💥
文章目錄
- Lua | 每日一練 (4)
- 題目
- 參考答案
- 線程和協程
- 調度方式
- 上下文切換
- 資源占用
- 實現機制
- 使用場景
- `lua` 中的協程
- 協程的生命周期
- 主要函數
- 創建協程
- 啟動或恢復協程
- 檢查當前是否在主協程中運行
- 暫停協程
- 檢測協程是否可暫停
- 獲取協程狀態
- 包裝函數
- 關閉協程
- 具體使用
Lua | 每日一練 (4)
題目
協程和線程有何區別?簡述 lua
中的協程。
參考答案
線程和協程
協程和線程雖然在某些方面有相似之處,但它們在設計目標、實現原理和使用方式上有很大的區別。下面從調用方式、上下文切換、資源使用、實現機制、使用場景這幾個方面進行闡述。
調度方式
- 線程:線程的執行由操作系統內核控制,操作系統會根據調度算法(如時間片輪轉、優先級調度等)自動切換線程的執行。另外,線程的切換時間點不可預測,程序無法直接控制線程的暫停和恢復。
- 協程:協程的執行由程序顯式控制,需要開發者通過
coroutine.yield
和coroutine.resume
顯式地暫停和恢復協程。協程的切換完全由程序邏輯決定,切換點是明確的。
上下文切換
- 線程:線程切換涉及操作系統內核的上下文切換,需要保存和恢復線程的寄存器狀態、棧信息等,開銷較大。
- 協程:協程的上下文切換在用戶態完成,不需要操作系統內核介入,開銷非常小。
資源占用
- 線程:每個線程都有自己的棧空間,通常默認分配 8 MB,資源占用較大。如果線程數量過多會導致系統資源耗盡。
$ ulimit -s
8192
- 協程:協程的棧空間是動態分配的,通常占用較少的內存。另外,協程的數量可以非常大,適合處理大規模的并發任務。
實現機制
- 線程:線程是操作系統提供的并發機制,由操作系統內核管理。線程的創建和銷毀需要系統調用,涉及內核態和用戶態的切換。
- 協程:協程是語言層面的機制,由
lua
解釋器實現。協程的創建和切換完全在用戶態完成,不涉及操作系統內核。
使用場景
- 線程:適合處理真正的并發任務,例如多核
CPU
上的并行計算,處理 I/O 密集型任務。 - 協程:適合處理單核
CPU
上的并發任務,尤其是需要頻繁切換的場景;適合實現非阻塞 I/O 操作,例如網絡編程中的異步請求處理。
lua
中的協程
在 lua
中,協程是實現異步編程的核心工具之一。由于 lua
本身沒有內置的多線程支持,而協程提供了一種輕量級的并發機制,可以用來模擬異步操作,從而實現非阻塞的程序設計。
協程的生命周期
lua
協程的生命周期包括以下幾個階段:
- 創建:使用
coroutine.create
創建一個協程。此時協程處于掛起狀態,尚未開始執行。 - 運行:使用
coroutine.resume
啟動或恢復協程的執行。 - 暫停:在協程執行過程中,可以通過
coroutine.yield
暫停協程的執行,將控制權交回主程序。 - 結束:當協程運行完成或因錯誤終止時,協程進入結束狀態。
協程的狀態之間切換,如下圖所示:
主要函數
下面介紹 lua
中關于協程的主要函數。
創建協程
local co = coroutine.create(f)
f
:協程函數,表示協程的主體邏輯。- 返回一個協程對象
co
,類型為thread
。協程對象可以用于后續的coroutine.resume
和coroutine.yield
等操作。
例如:
-- func 是協程函數,主體邏輯
local function func()print("Coroutine is running")
end-- 創建協程
local co = coroutine.create(func)
print(co) -- thread: 0x60f0c8666df8
啟動或恢復協程
ok, ... = coroutine.resume(co, ...)
co
:要啟動或者恢復的協程對象。...
:可選參數,這些參數會傳遞給協程中的coroutine.yield
或協程的入口函數。ok
:布爾值,表示協程是否成功恢復。如果協程因錯誤終止,返回false
。...
:協程中coroutine.yield
的返回值。如果協程運行完成,返回值為nil
。
說明:
- 如果協程處于 suspended 狀態,
coroutine.resume
會啟動或恢復協程的執行。 - 如果協程已經處于 running 狀態,調用
coroutine.resume
會拋出錯誤。 - 如果協程已經處于 dead 狀態,調用
coroutine.resume
也會拋出錯誤。
例如:
local function func(x, y)print("Coroutine started with:", x, y) -- Coroutine started with: 10 20
endlocal co = coroutine.create(func)local ok, result = coroutine.resume(co, 10, 20)
print(ok, result) -- true nil
檢查當前是否在主協程中運行
current_co, is_main = coroutine.running()
current_co
:當前運行的協程對象。is_main
:布爾值,表示當前是否在主協程中運行,如果是主協程返回true
,否則返回false
。
說明:
- 用于檢查當前是否在主協程中運行,以及當前協程是否是主線程。
例如:
local function printCurrentCoroutine()local current_co, is_main = coroutine.running()print(current_co, is_main)
endprintCurrentCoroutine() -- thread: 0x61617e0492a8 truelocal co = coroutine.create(function()print("Printing from the coroutine") -- Printing from the coroutineprintCurrentCoroutine() -- thread: 0x61617e04fec8 false
end)coroutine.resume(co) -- 啟動協程
暫停協程
... = coroutine.yield(...)
...
:可選參數,這些參數會傳遞給調用coroutine.resume
的代碼。- 返回值是
coroutine.resume
調用時傳遞的參數。
說明:
coroutine.yield
用于暫停當前協程的執行,并將控制權返回給調用coroutine.resume
的代碼。- 協程暫停后,可以通過再次調用
coroutine.resume
恢復執行。
例如:
local function func()print("Coroutine running") -- Coroutine runninglocal value = coroutine.yield("Yielded value")print("Coroutine resumed with:", value) -- Coroutine resumed with: Hello
endlocal co = coroutine.create(func)
local ok, result = coroutine.resume(co)
print(ok, result) -- true Yielded valuelocal ok, result = coroutine.resume(co, "Hello")
print(ok, result) -- true nil
檢測協程是否可暫停
is_yieldable = coroutine.isyieldable([co])
co
:可選參數,表示要檢查的協程對象。默認為當前運行的協程。is_yieldable
:布爾值,表示協程是否可以暫停。
說明:
- 如果協程不是主協程且不在非可暫停的
C
函數中,則返回true
。 - 主協程調用時返回
false
。 - 協程處于
suspended
、dead
狀態時coroutine.isyieldable
仍然返回true
,因為協程對象本身仍然是有效的,并且在lua
的語義中,dead
狀態的協程仍然可以被認為是可以暫停的(盡管它已經無法再被恢復)。
例如:
local co = coroutine.create(function()print(coroutine.isyieldable()) -- truecoroutine.yield()
end)coroutine.resume(co) -- 啟動協程
print(coroutine.isyieldable(co)) -- true
print(coroutine.status(co)) -- suspended
coroutine.resume(co)
print(coroutine.status(co)) -- dead
print(coroutine.isyieldable(co)) -- true
print(coroutine.isyieldable()) -- false
獲取協程狀態
status = coroutine.status(co)
co
:要檢查狀態的協程對象。- 返回一個字符串,表示協程的當前狀態:
- `running:協程正在運行。
suspended
:協程處于暫停狀態。dead
:協程已經運行完成或因錯誤終止。normal
:協程尚未啟動(初始狀態)。
例如:
local function func(co)local status = coroutine.status(co)print(status) -- runningcoroutine.yield("hello, world!")
endlocal co = coroutine.create(func)
local status = coroutine.status(co)
print(status) -- suspendedlocal ok, result = coroutine.resume(co, co)
print(ok, result) -- true hello, world!local ok, result = coroutine.resume(co, co)
print(ok, result) -- true nillocal status = coroutine.status(co)
print(status) -- dead
包裝函數
f = coroutine.wrap(f)
f
:協程函數,表示協程的主體邏輯。- 返回一個包裝函數
f
。調用這個包裝函數時,會自動啟動或恢復協程的執行。
說明:
coroutine.wrap
是coroutine.create
和coroutine.resume
的簡化版本。- 包裝函數的返回值是協程中
coroutine.yield
的參數。 - 如果協程運行完成,包裝函數返回
nil
;如果協程因錯誤終止,會拋出錯誤。
例如:
local wrapped = coroutine.wrap(function()print("Coroutine running")local value = coroutine.yield("Yielded value")print("Coroutine resumed with:", value)
end)local result = wrapped()
print(result) -- Yielded valuewrapped("Hello") -- Coroutine resumed with: Hello
關閉協程
ok, err = coroutine.close(co)
-
co
:要關閉的協程對象,協程的狀態必須是 suspended 或 dead 狀態。 -
ok
:布爾值,表示操作是否成功。 -
err
:如果操作失敗,返回錯誤信息。
說明:
- 關閉協程
co
,將其狀態設置為 dead,并關閉協程中所有待關閉的變量。 - 如果協程已經是 dead 狀態,調用
coroutine.close
會返回true
。
例如:
local co = coroutine.create(function()coroutine.yield() -- 暫停協程
end)coroutine.resume(co) -- 啟動協程
print(coroutine.status(co)) -- suspendedlocal ok, err = coroutine.close(co) -- 關閉協程
print(ok, err) -- true nil
print(coroutine.status(co)) -- dead
具體使用
使用協程實現經典的生產者-消費者模型。協程的暫停和恢復特性非常適合這種場景,因為生產者和消費者可以分別在協程中運行,通過共享隊列進行通信。
一個簡單的生產者-消費者模型實現,如下所示:
-- 隊列實現
local Queue = {}function Queue:new()local obj = {}setmetatable(obj, self)self.__index = selfobj.list = {}return obj
endfunction Queue:put(item)table.insert(self.list, item)
endfunction Queue:get()if #self.list <= 0 thenreturn nilendlocal item = self.list[1]table.remove(self.list, 1)return item
endfunction Queue:is_empty()return #self.list == 0
end-- 生產者函數
local function producer(queue, count)for i = 1, count doprint("Produced:", i)queue:put(i)coroutine.yield()end
end-- 消費者函數
local function consumer(queue, count)for i = 1, count dowhile queue:is_empty() doprint("Waiting for item...")coroutine.yield()endlocal item = queue:get()print("Consumed:", item)end
endlocal queue = Queue:new()
local count = 100 -- 生產/消費數量-- 創建生產者和消費者協程
local co_producer = coroutine.create(function()producer(queue, count)
end)local co_consumer = coroutine.create(function()consumer(queue, count)
end)-- 同時運行生產者和消費者協程
while coroutine.status(co_producer) ~= "dead" or coroutine.status(co_consumer) ~= "dead" doif coroutine.status(co_producer) ~= "dead" thencoroutine.resume(co_producer)endif coroutine.status(co_consumer) ~= "dead" thencoroutine.resume(co_consumer)end
end
🌺🌺🌺撒花!
如果本文對你有幫助,就點關注或者留個👍
如果您有任何技術問題或者需要更多其他的內容,請隨時向我提問。