协同

协同,即coroutine与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的命令指针,以及与其它的协同进程共享全局变量等等。

协同与线程的区别

一个具有多线程的进程可以同时运行几个线程,而协同进程却需要彼此协作的运行。

在任一指定时刻只有一个协同进程在运行,并且这个正在运行的协同进程只有在明确的被要求挂起的时候才会被挂起。

协同进程有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。

语法

方法 描述
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 的线程号

简单实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
--协同的实例
--这个是创建协同
xiecheng=coroutine.create(
function(i)
print(i);
end
)
--重启协同,与创建配合使用可以唤醒函数调用
coroutine.resume(xiecheng,1) --这个其实就是执行创建协同的内置函数
print(coroutine.status(xiecheng)) --查看当前协同的状态
print("----------")-- 打印一个分界线,方便看输出结果
--下面这个是创建协同同时进入协同,和创建一样的
xiecheng=coroutine.wrap(
function (i)
print(i);
end
)
xiecheng(i)

print("----------")

xiecheng2=coroutine.create(
function ()
for i=1,10 do
print(i)
if i==3 then
print(coroutine.status(xiecheng2))
print(coroutine.running()) --返回正在跑的协同
end
coroutine.yield() --将协同设置为挂起状态
end
end
)

coroutine.resume(xiecheng2)
coroutine.resume(xiecheng2)
coroutine.resume(xiecheng2)

print(coroutine.status(xiecheng2))
print(coroutine.running())
print("----------")

输出:

从running方法就可以看出,其实协同在底层的实现就是一个线程。

当create一个coroutine协同的时候就是在新线程中注册了一个事件。

当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。

生产者-消费者问题解决(协同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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()

结果:

I/O文档

两种模式

文档流的操作无论是在C++或者C中其实已经遇到很多次了,但是Lua中略微不同,如何不同?下面开始详细介绍。

首先,Lua中的IO库分为两种模式:

1
2
简单模式(simple model)拥有一个当前输入文档和一个当前输出文档,并且提供针对这些文档相关的操作。
完全模式(complete model) 使用外部的文档句柄来实现。它以一种面对对象的形式,将所有的文档操作定义为文档句柄的方法。

所谓的简单模式,其实就是C中使用的IO方法,但是对于一些简单的文档操作还好,如果进行一些对文档的高级操作的时候,就显得十分的乏力,例如同时读取多个文档?

打开文档操作

1
file = io.open(filename [, mode])

mode值如下:

模式 描述
r 以只读方式打开文档,该文档必须存在。
w 打开只写文档,若文档存在则文档长度清为0,即该文档内容会消失。若文档不存在则建立该文档。
a 以附加的方式打开只写文档。若文档不存在,则会建立该文档,如果文档存在,写入的数据会被加到文档尾,即文档原先的内容会被保留。(EOF符保留)
r+ 以可读写方式打开文档,该文档必须存在。
w+ 打开可读写文档,若文档存在则文档长度清为零,即该文档内容会消失。若文档不存在则建立该文档。
a+ 与a类似,但此文档可读可写
b 二进制模式,如果文档是二进制文档,可以加上b
+ 号表示对文档既可以读也可以写

简单模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
--以只读方式打开文档
file=io.open("IOTest.lua","r")

-- 设置默认的输入文档为IOTest.lua
io.input(file)

--输出文档的第一行
print(io.read())

--关闭打开的文档
io.close(file)

--以附加的方式打开只写文档
file=io.open("IOTest.lua","a")

--设置默认的输出文档为IOTest.lua
io.output(file)

--在文档最后一行添加lua注释
io.write("n -- 文档末尾注释")

--关闭打开文档
io.close(file)

结果:

如上图所示,使用完全没问题,但是只能进行简单的操作,只能适应简单的文档操作。

上图实例中io.read中其实可以带参数的,如下:

模式 描述
“*n” 读取一个数字并返回它。例:file.read(“*n”)
“*a” 从当前位置读取整个文档。例:file.read(“*a”)
“*l”(默认) 读取下一行,在文档尾 (EOF) 处返回 nil。例:file.read(“*l”)
number 返回一个指定字符个数的字符串,或在 EOF 时返回 nil。例:file.read(5)

其他的IO方法:

1
2
3
4
5
6
7
io.tmpfile():返回一个临时文档句柄,该文档以更新模式打开,进程结束时自动删除

io.type(file): 检测obj是否一个可用的文档句柄

io.flush(): 向文档写入缓冲中的所有数据

io.lines(optional file name): 返回一个迭代函数,每次调用将获得文档中的一行内容,当到文档尾时,将返回nil,但不关闭文档

安全模式

很多时候我们需要同时处理多个文档或者同时处理一个文档(多操作)。

简单实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 以只读方式打开文档
file = io.open("IOTest.lua", "r")

-- 输出文档第一行
print(file:read())

-- 关闭打开的文档
file:close()

-- 以附加的方式打开只写文档
file = io.open("test.lua", "a")

-- 在文档最后一行添加 Lua 注释
file:write("--test")

-- 关闭打开的文档
file:close()

结果:

注:这里我就不重写弄文档了,直接使用上个简单模式创建的测试IOTest.lua文档。

其他方法:

file:seek(optional whence, optional offset): 设置和获取当前文档位置,成功则返回最终的文档位置(按字节),失败则返回nil加错误信息。参数 whence 值可以是:

  • “set”: 从文档头开始
  • “cur”: 从当前位置开始[默认]
  • “end”: 从文档尾开始
  • offset:默认为0

不带参数file:seek()则返回当前位置,file:seek(“set”)则定位到文档头,file:seek(“end”)则定位到文档尾并返回文档大小

file:flush(): 向文档写入缓冲中的所有数据

io.lines(optional file name): 打开指定的文档filename为读模式并返回一个迭代函数,每次调用将获得文档中的一行内容,当到文档尾时,将返回nil,并自动关闭文档。若不带参数时io.lines() <=> io.input():lines(); 读取默认输入设备的内容,但结束时不关闭文档,