💢欢迎来到张胤尘的技术站
💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥
文章目录
- 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
🌺🌺🌺撒花!
如果本文对你有帮助,就点关注或者留个👍
如果您有任何技术问题或者需要更多其他的内容,请随时向我提问。