Lua学习笔记4. coroutine协同程序和文件I/O、错误处理

coroutine

Lua 的协同程序coroutine和线程比较类似,有独立的堆栈、局部变量、独立的指针指令,同时又能共享全局变量

但coroutine又和多线程程序不同,首先一个多线程程序可以同时运行多个单线程,但协同程序只能串行,也就是说同一时刻只能有一个协同程序在运行,并且这个协同程序将一直占用处理器直到被显式的挂起。

基本的函数

coroutine.create(f)         创建一个协同程序,返回coroutine, f是该协同程序的注册函数
coroutine.yield(a)          将当前运行的coroutine挂起,a是挂起后返回的值或表达式结果
coroutine.resume(co,a,...)  唤醒coroutine co,后面的a,...是对应注册函数待输入的参数,返回一个布尔值表示是否唤醒成功
coroutine.status(co)        返回协同程序co的状态,协同程序的状态包括dead,suspend和running
coroutine.running()         返回当前正在运行的coroutine的线程号和一个boolean值表示是不是主线程

看个例子

function foo(a)
    print("foo 的函数输出 ", a)
    return coroutine.yield(2*a)
end

co=coroutine.create(function(a,b)
    print("第一次协同程序执行输出",a,b)
    local r=foo(a+1)
    a=a+1
    print("第二次协同程序输出",r)
    local r,s=coroutine.yield(a+b,a-b)

    print("第三次协同程序执行输出",r,s)
    return b
    end
)

print('main',coroutine.resume(co,1,10))
print('--------分割线--------')
print('main',coroutine.resume(co))
print('--------分割线--------')
print("main",coroutine.resume(co,"x","y"))
print("--------分割线--------")
print("main",coroutine.resume(co,"x","y"))
print("--------分割线--------")

其输出

第一次协同程序执行输出 1   10
foo 的函数输出   2
main    true    4
--------分割线--------
第二次协同程序输出   nil
main    true    12  -8
--------分割线--------
第三次协同程序执行输出 x   y
main    true    10
--------分割线--------
main    false   cannot resume dead coroutine
--------分割线--------

分析一下:首先执行到print('main',coroutine.resume(co,1,10))这一行先执行里面作为参数的函数,即唤醒协同程序co,进入了对应的注册函数,输出一句,然后进入函数foo,foo的参数为1,10

执行foo里面的第一句输出,然后返回 挂起线程操作的状态,以及对应的返回值,这里就是2*a

OK,这时候又回到了主线程,输出 main 状态 返回参数

输出分割线

唤醒co,这个时候从上次挂起的阶段继续执行,此时resume的参数是coroutine.yield()的参数作为返回值

。。。

最后如果co已经dead,则输出false,即唤醒失败,和cannot resume dead coroutine的信息,如果这个时候想再执行注册函数的内容需要重新创建coroutine

生产者-消费者问题

local newProductor

function productor()
    local i=0
    while i<10 do
        i=i+1
        send(i)
    end
end

function consumer()
    repeat
        local i=receive()
        print(i)
    until i>9
end

function receive()
    local status,value=coroutine.resume(newProductor)
    return value
end

function send(x)
    coroutine.yield(x)
end

newProductor=coroutine.create(productor)
consumer()

I/O

lua的文件输入输出包含两种模式 简单模式和完全模式

简单模式

file = io.open("moduleTest.lua",'r')
io.input(file)
print(io.read())
io.close(file)
file=io.open("moduleTest.lua","a+")
io.output(file)
io.write("This is a sentense written in simple model")
io.close(file)

复杂模式

file=io.open("moduleTest.lua","a+")
print(file:read())
file:close()
file=io.open("moduleTest.lua","a")
file:write("This sentence is written in complete model")
file:close

不同点在于简单模式需要指定输入输出设备,而完全模式直接通过文件句柄操作。

文件open的模式有

r           只读模式,文件必须存在
w           清零重写模式,如果没有则新建
a           附加模式,没有则新建
r+          读写模式,文件必须存在
w+          和w类似,但是可读写
a+          和a类似,但是可读写
b           二进制模式,如果文件是二进制文件,可在上面的模式符后添加b

read()的参数有

空                  读取一行
"*n"                读取一个数字
"*a"                从当前位置读取整个文件
"*l"(默认)          都去下一行,文件结尾处返回nil
number              读取指定个数个字符,EOF时返回nil

io其他方法

io.tmpfile()    返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
io.type(file)   检测file是不是可用的文件句柄
io.flush()      向文件中写入缓存区中所有数据
io.lines(optional filename)     返回一个由line组成的迭代器,文件结尾时返回nil,但不关闭文件
e.g. 
for line in io.lines("moduleTest.lua") do 
    print(line)
end

file的read,write,lines,close等方法和io类似,只不过使用的是":"符号

file的其他方法

file:seek(optional whence,optional offset)  设定当前位置,第二个参数是偏置
第一个参数可选
"set"       文件头
"cur"       当前位置(默认)
"end"       文件尾
不带参数,file:seek()返回当前位置

file:flush()        将缓冲区所有数据写入文件
file:lines()        类似于io.lines(filename)返回一个迭代器,只是此处没有输入参数

错误处理

  1. 断言assert(arg1,arg2),首先检查第一个参数,没问题ok,有问题将第二个参数作为错误信息抛出

  2. error(message[,level]),终止执行的函数,抛出message作为错误信息

    level表示附加的错误位置信息

    level=1,error的位置

    level=2,调用error的函数

    level=0,不添加错误位置信息

  1. 类似于trycatch语句pcall,xpcall

    调用格式:

    pcall(f[,arg]) --f是protect call的函数,arg是f的参数

    xpcall(f,errHandleFun[,arg]) --errHandleFun是错误处理函数

相同点:

当程序正常执行时,都返回true和被执行函数f的返回值

不同点:

i. pcall: 返回错误信息时,已经释放了保存错误发生情况的栈信息

xpcall: 在释放栈信息之前调用错误处理程序处理这些信息

ii. pcall:返回nil,错误信息

xpcall:返回nil,无错误信息

local f=function(...)
        --local a=1
        print (a+1)
        return a+1
    end

tryCatch = function(f)
        local ret,errMessage=pcall(f)
        print("ret:" ..(ret and "true" or "false") .."\nerrMessage:"..(errMessage or "null"))
end

xTryCatchGetErrorInfo=function()
            print(debug.traceback())
        end

xTryCatch=function(f)
    local ret,errMessage=xpcall(f,xTryCatchGetErrorInfo)
    print("ret:"..(ret and "true" or "false").."\nerrMessage:"..(errMessage or "null"))
end

    print("\n---------A---------\n")
    tryCatch(f)
    print("\n---------B---------\n")
    xTryCatch(f)
    print("\n---------C---------\n")

输出

---------A---------

ret:false
errMessage:testIO.lua:18: attempt to perform arithmetic on global 'a' (a nil value)

---------B---------

stack traceback:
    testIO.lua:28: in function <testIO.lua:27>
    testIO.lua:18: in function <testIO.lua:16>
    [C]: in function 'xpcall'
    testIO.lua:32: in function 'xTryCatch'
    testIO.lua:39: in main chunk
    [C]: ?
ret:false
errMessage:null

---------C---------

>Exit code: 0