Programming in Lua, 2nd edition - Chapter 9: Coroutines

本文深入探讨Lua中的协程概念,包括基本用法、生产者-消费者模型的应用、作为迭代器的功能以及非抢占式多线程的实践案例。

 

 

 

Chapter 9: Coroutines

 

协程是一种非抢占式多线程。当一个协程运行时,没有办法从外部将其停止,只有等它自已主动进入休眼状态(调用yield)或自行终止对协程来说,各个任务运行于独立的协程中在协程间进行切换的代价大体上和函数调用相当。(协程的运行过程是执行-暂停、执行-暂停,

 

协程与线程类似:是一串操作,有自已的栈、局部变量和指令指针;但是与其它协程共享全局变量和几乎其它程何东西。 协程和线程的主要不同是,线程是并发执行的(在多核环境下),协程不是

 

协程非常有用,也比较复杂。

 

9.1 Coroutine Basics

 

Lua 将与协程相关的函数都封装在coroutine 表中。coroutine 表的create 函数创建新协程。create 的参数是欲运行的函数。

 

co = coroutine.create(function () print("hi") end)

print(co) --> thread: 0x8071d98

 

一个协程可以有四种状态:挂起、运行中、死亡和正常。

 

刚创建的协程处于挂起状态

 

检查协程的当前状态

 

print(coroutine.status(co)) --> suspended

 

激活resume协程

 

coroutine.resume(co) --> hi  

 

上面的代码先打印hi,然后进入dead 状态,这之前协程不会返回

 

print(coroutine.status(co)) --> dead

 


 

协程的强大源于yield 函数

 

co = coroutine.create(function ()

       for i=1,3 do

              print("co", i)

              coroutine.yield()               -- yield 函数主动挂起协程

       end

end)

 

coroutine.resume(co)                         --> co 1

print(coroutine.status(co))                   --> suspended

coroutine.resume(co)                         --> co 2

coroutine.resume(co)                         --> co 3

coroutine.resume(co)               --> 这个调用导至协程变为dead 状态

print(coroutine.resume(co))                --> false  cannot resume dead coroutine

 

对状态是dead 的协程,resume 函数返回false 加上错误消息。

 

注意resume 运行于保护模式pcall?)。因为这使得协程内部出错时能够有机会显示错误消息。

 

当一个协程激活resumes 另一个协程,它不会挂起也不是运行状态,而是正常状态(normal)

 

一个有用的特性是,在一对resum yield 之间可以交换数据

 

 

给协程的主函数传递参数,主函数的返回值又传回resume

 

co = coroutine.create(function (a,b,c)

print("co", a,b,c)

end)

coroutine.resume(co, 1, 2, 3) --> co 1 2 3    -- 打印完后,进入dead 状态(那个匿名函数运行完了)

 


 

yield 函数计算传给它的参数表达式,并返回true 加结果。

 

co = coroutine.create(function (a,b)

coroutine.yield(a + b, a - b)

end)

print(coroutine.resume(co, 20, 10))     --> true 30 10

print(coroutine.resume(co, 20, 10))     --> true

print(coroutine.resume(co, 20, 10))     --> false  cannot resume dead coroutine

 

 

不经过协程的主函数,直接给yield 传递参数

 

co = coroutine.create (function ()

                   print("co", coroutine.yield())   -- yield() 第一次暂停,第二次返回4,5 然后协程dead

         end)

coroutine.resume(co)

coroutine.resume(co, 4, 5) --> co 4 5

 

 

最后,当一个协程终上,任何由它的主函数返回的值都传到相应的resume

(主函数既传给create 的那个函数)

co = coroutine.create(function ()

       return 6, 7

  end)

 

print(coroutine.resume(co)) --> true 6 7

 

 

我们很少在同一个协程中使用所有这些特性,但它们各有各的用处。

 

在我们继续深入之前先澄清一些概念是重要的。Lua 提供一种称为asymmetric coroutines 的机制(非对称协程)。意思是,它有一个函数挂起一个协程的执行,还有另一个函数恢复协程的执行。其它一些语言提供symmetric coroutines(对称协程),它们只用一个函数来控制从一个协程到另一个协程的转换。

 

一些人管非对称协程叫semi-coroutines。但是其它人也用同样的术语称呼协程的受限实现,这种实现中只有当一个协程不调用任何函数时能挂起自已。Python generator 就是一个semi-coroutines 的例子

 

 

9.2 Pipes and Filters

 

协程中最特别的一个例子是生产者消费者问题。让我们假设我们有一个函数,它不断的生产值(例如,读一个文件),而另一个函数不断的消费这些值(例如,把这些值写入另一个文件)。

 

产者消费者问题

 

function producer ()

while true do

local x = io.read()              -- produce new value

send(x)                                -- send to consumer

end

end

function consumer ()

while true do

local x = receive()              -- receive from producer

io.write(x, "/n")        -- consume new value

end

end

 

(这个例子中生产者和消费者都永运不停的运行,容易改成当没有要处理的数据时停止)这里的问题是如何协调send receive(比如它们的速度)。这是一个典型的谁拥有主循环的问题

对生产者和消费者来说,两者都是活动的,都有主循环,两者都假定对方是可随时提供服务的。

 

协程为生产者消费者间的调度提供了理想的工具

 


 

消费者驱动设计

 

function receive ()

       local status, value = coroutine.resume(producer)

       return value

end

 

function send (x)

       coroutine.yield(x)

end

 

producer = coroutine.create(

       function ()

              while true do

              local x = io.read() -- produce new value

              send(x)

              end

  end)

 

当然,生产者现在必须做为一个协程。在这个设计中,程序从调用消费者开始,当消费者需要一个数据,它激活(resumes 生产者,然后生产者一直运行,一旦生产出一个给消费者的东西就休眠(suspended),直到再一次被消费者激活。所以,这个设计称为消费者驱动设计(consumer-driven)。

 

我们可以用过滤器filters)扩展这个设计,过滤器在生产者和消费者之间做一些数据处理的任务。过滤器激活生产者得到一个新值,进行适当转换后把它传给消费者。

 

 

过滤器

 

function receive (prod)

       local status, value = coroutine.resume(prod)

       return value

end

 

function send (x)

       coroutine.yield(x)

end

 

function producer ()

       return coroutine.create(function ()

              while true do

                     local x = io.read() -- produce new value

                     send(x)

              end

      end)

end

 

function filter (prod)

       return coroutine.create(function ()

              for line = 1, math.huge do

                     local x = receive(prod) -- get new value

                     x = string.format("%5d %s", line, x)

                     send(x) -- send it to consumer

              end

         end)

end

 

function consumer (prod)

       while true do

              local x = receive(prod) -- get new value

              io.write(x, "/n") -- consume new value

       end

end

 

p = producer()

f = filter(p)

consumer(f)

------------------------------

-- consumer(filter(producer()))  -- 也可以这样

 

程序先创建一个协程,这个协程就是生产者。再创建一个协程,这个协程就是过滤器,将生产者作为参数传给它。将过虑器作为参数传给消费者。消费者在一个无限循环里面不断的消费产品。每一次消费产品的过程是:消费者先resume激活过虑器,然后过虑器resume激活生产者,它生产一个产品(从什么地方读一个数据),并放在yield 里面就挂起了。过虑器从resume 函数得到产品并进行处理(格式化从io.read获得的string,在前面加上行号做为新产品),将新产品放在yield 里面然后挂起。最后消费者从resume 函数得到最终产品。处理完产品后(将串打印出来),新一轮消费又开始了。

 


 

看过前面协程的例子会让人想起Unix 的管道。毕竟,协程是多线程的一种(非抢占式的)

对管道来说,各个任务运行于独立的进程中,而对协程来说,各个任务是运行于独立的协程中。管道为读和写(既生产者与消费者)提供了一个缓冲区,所以他们之间的相对速度获得了一定的自由。在管道的环境中这是重要的,因为在进程间进行切换代价高昂。而在协程间进行切换的开销更小,大体上和函数调用相当。所以,写入器和读取器可以交替运行。

 


 

9.3 Coroutines as Iterators

 

我们可以将迭代循环看成是生产者-消费者模型的一个特例

 

迭代器产生Item 供循环体消费。因此,看起来用协程来写迭代器是合适的。当然,协程为此任务提供了一个强有力的工具。

 

输出一串数字的全排列(为协程迭代器作准备工作)

 

function permgen (a, n)

       n = n or #a -- default for 'n' is size of 'a'

       if n <= 1 then -- nothing to change?

              printResult(a)

       else

              for i=1,n do

                     -- put i-th element as the last one

                     a[n], a[i] = a[i], a[n]

                     -- generate all permutations of the other elements

                     permgen(a, n - 1)

                     -- restore i-th element

                     a[n], a[i] = a[i], a[n]

              end

       end

end

 

function printResult (a)

       for i = 1, #a do

              io.write(a[i], " ")

       end

       io.write("/n")

end

 

function printResult (a)

       for i = 1, #a do

              io.write(a[i], " ")

       end

       io.write("/n")

end

permgen ({1,2,3,4})  -- 表不要太大了,不然运行起来像无限循环

 

 

这样,我们就有了一个生成器(生成全排列的生成器),接下来的任务是将这个生成器转换成迭代器。首先,我们在permgen 中应用yield

 

function permgen (a, n)

n = n or #a

if n <= 1 then

coroutine.yield(a)

else

<as before>  -- 后面的代码和之前一样

 

然后,我们定义一个工厂,它整理生成器,运行内部的协程,然后创建一个迭代函数。生成器只是简单的激活resume 协程来产生下一个排列。

 

 

生成全排列的协程迭代器

 

function permutations (a)

         local co = coroutine.create(function () permgen(a) end)

         return function () -- iterator

                   local code, res = coroutine.resume(co)

                   return res

           end

end

 

function permgen (a, n)

         n = n or #a -- default for 'n' is size of 'a'

         if n <= 1 then -- nothing to change?

                   coroutine.yield(a)

         else

                   for i=1,n do

                            -- put i-th element as the last one

                            a[n], a[i] = a[i], a[n]

                            -- generate all permutations of the other elements

                            permgen(a, n - 1)

                            -- restore i-th element

                            a[n], a[i] = a[i], a[n]

                   end

         end

end

 

function permutations (a)

         local co = coroutine.create(function () permgen(a) end)

         return function () -- iterator

                   local code, res = coroutine.resume(co)

                   return res

           end

end

 

function printResult (a)

         for i = 1, #a do

                   io.write(a[i], " ")

         end

         io.write("/n")

end

 

for p in permutations{"a", "b", "c"} do

         printResult(p)

end

 

-----------------------------------------------------------------

function permutations (a)

       return coroutine.wrap(function () permgen(a) end)

end

 

 

coroutine.wrap create 一样,创建一个新协程。不同的是,它不返回协程本身,而是返回一个函数,被调用时激活resumes 协程。

 

coroutine.wrap 不返回错误码作为第一个返回值,而是直接引发错误。

 

通常,coroutine.wrap coroutine.create 更易使用。它从协程给我们真正想要的:resume 一个函数。但是,其灵活性较低。没有办法检查使用wrap 建创的协程的状态。此外,不能检查运行时错误。

 


 

9.4 Non-Preemptive Multithreading

 

协程是一种非抢占式多线程。当一个协程运行时,没有办法从外部将其停止,只有等它自已主动进入休眼状态(调用yield)或自行终止

 

应用程序中缺乏抢占机制不会成为一个问题。相反,缺乏抢占机制使得编程更容易了。你无需担心同步bug。你只需确保协程只在出了边界时休眠yields

 

协程正在操作时,整个程序只有等它执行完才能继续后面的工作。对多数应用程序来说,这是不可接受的行为

 

让我们考虑一个典型的多线程情形:我们想要从HTTP 下载几个远程文件。这个例子用到了由Diego Nehab 开发的LuaSocket,要下载一个文件必须建立一个到站点的连接,发送文件请求,接收文件,然后关闭连接。

 

首先,加载LuaSocket 库:

 

require "socket"

 

然后,定义要从什么主机下载什么文件(这里是HTML3.2 规范文件):

 

host = "www.w3.org"

file = "/TR/REC-html32.html"    -- HTML 3.2 Reference Specification

 

接下来打开一个到80 端口的TCP 连接:

 

c = assert(socket.connect(host, 80))

 

socket.connect 返回一个连接对象,我们通过它发送文件请求:

 

c:send("GET " .. file .. " HTTP/1.0/r/n/r/n")

 

下一步,我们分块读取文件,每次读1K,将每一块写到标准输出:


 

while true do

local s, status, partial = c:receive(2^10)

io.write(s or partial)

if status == "closed" then break end

end

 

receive 返回读到的string nil(出错时),后一种状况会同时返回错误码(status) partial 是出错时读到的东西。

 

最后关闭连接:

 

c:close()

 

下载单个文件

 

require "socket"

 

host = "www.w3.org"

file = "/TR/REC-html32.html"    -- HTML 3.2 Reference Specification

 

c = assert(socket.connect(host, 80))

c:send("GET " .. file .. " HTTP/1.0/r/n/r/n")

while true do

       local s, status, partial = c:receive(2^10)

       io.write(s or partial)

       if status == "closed" then break end

end

 

c:close()

 

 

现在,我们知道了如何下载一个文件,让我们回到下载多个文件的问题上。

当读一个远程文件,程序大多数时间都花在等特数据到达。如果同时下载多个文件速度更快,当一个连接没有可用数据,程序可以从另一个连接读。协程为同时下载提供了便利的方法,我们为每一个下载任务创建一个新线程,当一个线程不再有可用的数据,它将控制权交给调度器,后者调用另一个线程。

 

 

现在receive 的实现不能再将数据分块,如果没有足够的数据,它就休眠yields

 

function receive (connection)

         connection:settimeout(0) -- do not block

         local s, status, partial = connection:receive(2^10)

         if status == "timeout" then

                   coroutine.yield(connection)

         end

         return s or partial, status

end

 

调用settimeout(0) 使得对连接的任何操作成为非阻塞操作。当操作状态是“timeout”,意思是那个操作没有完成就返回了。这种情况下,线程休眠yields传给yield non-false 参数通知调度器线程仍在执行它的任务

 

线程表为分派器保存一个所有活动线程的列表。get 函数确保每个下载运行于独立的程线。调度器本身是一个主要的循环,它遍历所有线程,一个接一个的激活它们。调度器也必须从列表里移除那些已经完成任务的线程;当不再有线程运行的时侯,调度器就终止循环。

 

最后,是程序的主要部分,它根椐需要创建线程并且调用分派器。

 

多线程下载(这个实现消耗较多的CPU时间,改进版见后面)

 

require "socket"

 

function download (host, file)

       local c = assert(socket.connect(host, 80))

       local count = 0 -- counts number of bytes read

       c:send("GET " .. file .. " HTTP/1.0/r/n/r/n")

       while true do

              local s, status, partial = receive(c)

              count = count + #(s or partial)

              if status == "closed" then break end

       end

       c:close()

       print(file, count)

end

 

function receive (connection)

       connection:settimeout(0) -- do not block

       local s, status, partial = connection:receive(2^10)

       if status == "timeout" then

              coroutine.yield(connection)

       end

       return s or partial, status

end

 

threads = {} -- list of all live threads

 

function get (host, file)

       -- create coroutine

       local co = coroutine.create(function ()

              download(host, file)

         end)

       -- insert it in the list

       table.insert(threads, co)

end

 

function dispatch ()

       local i = 1

       while true do

              if threads[i] == nil then -- no more threads?

                     if threads[1] == nil then break end -- list is empty?

                     i = 1 -- restart the loop

              end

              local status, res = coroutine.resume(threads[i])

              if not res then -- thread finished its task?

                     table.remove(threads, i)

              else

                     i = i + 1

              end

       end

end

 

host = "www.w3.org"

get(host, "/TR/html401/html40.txt")

get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")

get(host, "/TR/REC-html32.html")

get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")

dispatch() -- main loop

 

 

在我的机器上使用协程下载这四个文件需要4 秒,而使用顺序下载的时间超过这个时间的两倍(15 )

 

尽管速度变快了,但离最佳实现还差很远。当至少有一个线程有数据读时一切都很好。但是,当所有线程都无事可做时,分派器还是会一个接一个的检查线程,它们仍然没有数据。结果,这个协程实现比顺序操作方安案所使用的CPU 时间要多30

 

要避免这个行为,我们可以使用LuaSocket select 函数。它允许一个程序阻塞,等待一组socket 的其中一个状态改变

 

我们只需改变分派器,新的分派器在循环中从连接表中收集“time-out”连接。如果所有连接都超时,分派器调用select 函数等待这些连接的任意一个改变状态。这个实现只比顺序操作方案使用的CPU  时间稍多。

 

多线程下载

 

require "socket"

 

function download (host, file)

       local c = assert(socket.connect(host, 80))

       local count = 0 -- counts number of bytes read

       c:send("GET " .. file .. " HTTP/1.0/r/n/r/n")

       while true do

              local s, status, partial = receive(c)

              count = count + #(s or partial)

              if status == "closed" then break end

       end

       c:close()

       print(file, count)

end

 

function receive (connection)

       connection:settimeout(0) -- do not block

       local s, status, partial = connection:receive(2^10)

       if status == "timeout" then

              coroutine.yield(connection)

       end

       return s or partial, status

end

 

threads = {} -- list of all live threads

 

function get (host, file)

       -- create coroutine

       local co = coroutine.create(function ()

              download(host, file)

         end)

       -- insert it in the list

       table.insert(threads, co)

end

 

function dispatch ()

       local i = 1

       local connections = {}

       while true do

              if threads[i] == nil then -- no more threads?

                     if threads[1] == nil then break end

                     i = 1 -- restart the loop

                     connections = {}

              end

              local status, res = coroutine.resume(threads[i])

              if not res then -- thread finished its task?

                     table.remove(threads, i)

              else -- time out

                     i = i + 1

                     connections[#connections + 1] = res

                     if #connections == #threads then -- all threads blocked?

                            socket.select(connections)

                     end

              end

       end

end

 

host = "www.w3.org"

get(host, "/TR/html401/html40.txt")

get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")

get(host, "/TR/REC-html32.html")

get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")

dispatch() -- main loop

 

 

Contents Preface xiii I The Language 1 1 Getting Started 3 1.1 Chunks 4 1.2 Some Lexical Conventions 5 1.3 Global Variables 6 1.4 The Stand-Alone Interpreter 7 2 Types and Values 9 2.1 Nil 10 2.2 Booleans 10 2.3 Numbers 10 2.4 Strings 11 2.5 Tables 13 2.6 Functions 17 2.7 Userdata and Threads 17 3 Expressions 19 3.1 Arithmetic Operators 19 3.2 Relational Operators 20 3.3 Logical Operators 21 3.4 Concatenation 22 3.5 Precedence 22 3.6 Table Constructors 22 vii Property of Ian Bloss <ianlinkcd@gmail.com> viii Contents 4 Statements 27 4.1 Assignment 27 4.2 Local Variables and Blocks 28 4.3 Control Structures 30 4.4 break and return 34 5 Functions 35 5.1 Multiple Results 36 5.2 Variable Number of Arguments 39 5.3 Named Arguments 42 6 More About Functions 45 6.1 Closures 47 6.2 Non-Global Functions 50 6.3 Proper Tail Calls 52 7 Iterators and the Generic for 55 7.1 Iterators and Closures 55 7.2 The Semantics of the Generic for 57 7.3 Stateless Iterators 58 7.4 Iterators with Complex State 60 7.5 True Iterators 61 8 Compilation, Execution, and Errors 63 8.1 Compilation 63 8.2 C Code 67 8.3 Errors 67 8.4 Error Handling and Exceptions 69 8.5 Error Messages and Tracebacks 70 9 Coroutines 73 9.1 Coroutine Basics 73 9.2 Pipes and Filters 76 9.3 Coroutines as Iterators 79 9.4 Non-Preemptive Multithreading 81 10 Complete Examples 87 10.1 Data Description 87 10.2 Markov Chain Algorithm 91 II Tables and Objects 95 11 Data Structures 97 11.1 Arrays 97 Property of Ian Bloss <ianlinkcd@gmail.com> ix 11.2 Matrices and Multi-Dimensional Arrays 98 11.3 Linked Lists 100 11.4 Queues and Double Queues 100 11.5 Sets and Bags 101 11.6 String Buffers 103 11.7 Graphs 104 12 Data Files and Persistence 107 12.1 Data Files 107 12.2 Serialization 109 13 Metatables and Metamethods 117 13.1 Arithmetic Metamethods 118 13.2 Relational Metamethods 120 13.3 Library-Dened Metamethods 122 13.4 Table-Access Metamethods 122 14 The Environment 129 14.1 Global Variables with Dynamic Names 129 14.2 Global-Variable Declarations 131 14.3 Non-Global Environments 132 15 Modules and Packages 137 15.1 The require Function 138 15.2 The Basic Approach for Writing Modules 141 15.3 Using Environments 143 15.4 The module Function 144 15.5 Submodules and Packages 145 16 Object-Oriented Programming 149 16.1 Classes 151 16.2 Inheritance 152 16.3 Multiple Inheritance 154 16.4 Privacy 156 16.5 The Single-Method Approach 158 17 Weak Tables 161 17.1 Memoize Functions 163 17.2 Object Attributes 164 17.3 Revisiting Tables with Default Values 165 III The Standard Libraries 167 18 The Mathematical Library 169 Property of Ian Bloss <ianlinkcd@gmail.com> x Contents 19 The Table Library 171 19.1 Insert and Remove 171 19.2 Sort 172 19.3 Concatenation 173 20 The String Library 175 20.1 Basic String Functions 175 20.2 Pattern-Matching Functions 177 20.3 Patterns 180 20.4 Captures 183 20.5 Replacements 185 20.6 Tricks of the Trade 189 21 The I/O Library 193 21.1 The Simple I/O Model 193 21.2 The Complete I/O Model 196 21.3 Other Operations on Files 199 22 The Operating System Library 201 22.1 Date and Time 201 22.2 Other System Calls 203 23 The Debug Library 205 23.1 Introspective Facilities 205 23.2 Hooks 210 23.3 Proles 211 IV The C API 215 24 An Overview of the C API 217 24.1 A First Example 218 24.2 The Stack 221 24.3 Error Handling with the C API 225 25 Extending Your Application 229 25.1 The Basics 229 25.2 Table Manipulation 231 25.3 Calling Lua Functions 235 25.4 A Generic Call Function 236 26 Calling C from Lua 241 26.1 C Functions 241 26.2 C Modules 244 Property of Ian Bloss <ianlinkcd@gmail.com> xi 27 Techniques for Writing C Functions 247 27.1 Array Manipulation 247 27.2 String Manipulation 249 27.3 Storing State in C Functions 251 28 User-Dened Types in C 259 28.1 Userdata 260 28.2 Metatables 262 28.3 Object-Oriented Access 265 28.4 Array Access 267 28.5 Light Userdata 268 29 Managing Resources 269 29.1 A Directory Iterator 269 29.2 An XML Parser 271 30 Threads and States 281 30.1 Multiple Threads 281 30.2 Lua States 285 31 Memory Management 293 31.1 The Allocation Function 293 31.2 The Garbage Collector 295 Index 299
目前很多程序语言都专注于帮你编写成千上万行的代码,所以此类型的语言所提供的包、命名空间、复杂的类型系统及无数的结构,有上千页的文档需要操作者学习。 而Lua并不帮你编写大量的代码的程序,相反的,Lua仅让你用少量的代码解决关键问题。为实现这个目标,像其他语言一样Lua依赖于其可扩展性。但是与其他语言不同的是,不仅用Lua编写的软件易于扩展,而且用其他语言比如C/C++编写的软件也很容易使用Lua扩展其功能。 一开始,Lua就被设计成很容易和传统的C/C++整合的语言。这种语言的二元性带来了极大的好处。Lua是一个小巧而简单的语言,因为Lua不致力于做C语言已经做得很好的领域,比如:性能、底层操作以及与第三方软件的接口。Lua依赖于C去做完成这些任务。Lua所提供的机制是C不善于的:高级语言、动态结构、简洁、易于测试和调试等。正因为如此,Lua具有良好的安全保证,自动内存管理,简便的字符串处理功能及其他动态数据的改变。 Lua不仅是一种易于扩展的语言,也是一种易整合语言(glue language);Lua支持基于组件的,我们可以将一些已经存在的高级组件整合在一起实现一个应用软件。一般情况下,组件使用像C/C++等静态的语言编写。但Lua是我们整合各个组件的粘合剂。又通常情况下,组件(或对象)表现为具体在程序开发过程中很少变化的、占用大量CPU时间的决定性的程序,例如窗口部件和数据结构。对那种在产品的生命周期内变化比较多的应用方向使用Lua可以更方便的适应变化。除了作为整合语言外,Lua自身也是一个功能强大的语言。Lua不仅可以整合组件,还可以编辑组件甚至完全使用Lua创建组件。 除了Lua外,还有很多类似的脚本语言,例如:Perl、Tcl、Ruby、Forth、Python。虽然其他语言在某些方面与Lua有着共同的特色,但下面这些特征是Lua特有的: ① 可扩展性。Lua的扩展性非常卓越,以至于很多人把Lua用作搭建领域语言的工具(注:比如游戏脚本)。Lua被设计为易于扩展的,可以通过Lua代码或者C代码扩展,Lua的很多功能都是通过外部库来扩展的。Lua很容易与C/C++、java、fortran、Smalltalk、Ada,以及其他语言接口。 ② 简单。Lua本身简单,小巧;内容少但功能强大,这使得Lua易于学习,很容易实现一些小的应用。他的完全发布版(代码、手册以及某些平台的二进制文件)仅用一张软盘就可以装得下。 ③ 高效率。Lua有很高的执行效率,统计表明Lua是目前平均效率最高的脚本语言。 ④ 与平台无关。Lua几乎可以运行在所有我们听说过的系统上,如NextStep、OS/2、PlayStation II (Sony)、Mac OS-9、OS X、BeOS、MS-DOS、IBM mainframes、EPOC、PalmOS、MCF5206eLITE Evaluation Board、RISC OS,及所有的Windows和Unix。Lua不是通过使用条件编译实现平台无关,而是完全使用ANSI (ISO) C,这意味着只要你有ANSI C编译器你就可以编译并使用LuaLua大部分强大的功能来自于他的类库,这并非偶然。Lua的长处之一就是可以通过新类型和函数来扩展其功能。动态类型检查最大限度允许多态出现,并自动简化调用内存管理的接口,因为这样不需要关心谁来分配内存谁来释放内存,也不必担心数据溢出。高级函数和匿名函数均可以接受高级参数,使函数更为通用。 Lua自带一个小规模的类库。在受限系统中使用Lua,如嵌入式系统,我们可以有选择地安装这些类库。若运行环境十分严格,我们甚至可以直接修改类库源代码,仅保留需要的函数。记住:Lua是很小的(即使加上全部的标准库)并且在大部分系统下你仍可以不用担心的使用全部的功能。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值