[转载]协程-cooperative multitasking
[转载]协程三讲
http://ravenw.com/blog/2011/08/24/coroutine-part-1-defination-and-classification-of-coroutine/
http://ravenw.com/blog/2011/09/01/coroutine-part-2-the-use-of-coroutines/
http://ravenw.com/blog/2011/09/06/coroutine-part-3-coroutine-and-continuation/
协程(一)协程的定义与分类
由于协程所带来的便利,以及使用时产生的疑惑,我深入了解了一番这个概念。回头来看,目前网上能查到的关于协程的资料实在不多,而且多数都会造 成一些迷惑和误解(主要是下文提到的概念模糊问题)。于是我决定写个系列来详细介绍这个概念,一方面加深自己的理解,一方面培养点开放共享的精神,同时也 期待大牛的指正。
协程的定义
协程的概念最早由Melvin Conway在1963年提出并实现,用于简化COBOL编译器的词法和句法分析器间的协作,当时他对协程的描述是“行为与主程序相似的子例程”。
Wiki的定义:协程是一种程序组件,是由子例程(过程、函数、例程、方法、子程序)的概念泛化而来的,子例程只有一个入口点且只返回一次,而协程允许多个入口点,可以在指定位置挂起和恢复执行。
这是从直观上对协程的概念所做的理解,与1980年Marlin的论文中给出的定义类似,也是被广为引用的协程定义:
- 协程的本地数据在后续调用中始终保持
- 协程在控制离开时暂停执行,当控制再次进入时只能从离开的位置继续执行
来看Wiki举出的,也是解释协程时最常见的生产-消费者模型的例子:
1 var q := new queue
2
3 coroutine produce
4 loop
5 while q is not full
6 create some new items
7 add the items to q
8 yield to consume
9
10 coroutine consume
11 loop
12 while q is not empty
13 remove some items from q
14 use the items
15 yield to produce
这个例子中容易让人产生疑惑的一点就是yield的使用,它与我们通常所见的yield指令不同。这是因为我们常见的yield指令大都是基于生成器(Generator)这一概念的。下面是基于生成器的生产-消费者模型实现(依然来自Wiki):
1 var q := new queue
2
3 generator produce
4 loop
5 while q is not full
6 create some new items
7 add the items to q
8 yield consume
9
10 generator consume
11 loop
12 while q is not empty
13 remove some items from q
14 use the items
15 yield produce
16
17 subroutine dispatcher
18 var d := new dictionary (generator → iterator)
19 d[produce] := start produce
20 d[consume] := start consume
21 var current := produce
22 loop
23 current := next d[current]
根据大部分网上资料(包括Wiki)的解释,这是基于生成器实现了协程。但根据之前协程的定义:1)本地数据在后续调用中始终保持,2)控制离开时 挂起,重新进入时继续执行。我们看这里的produce与consume过程,完全符合协程的概念。也就是说,根据定义,生成器本身就是协程。
两种明显不同的控制结构,却都符合协程的定义,问题出在哪里?
协程的分类
之前的协程定义的问题在于,这个定义不够精确,遗留下了开放的,关于协程结构的问题。这导致了协程概念的模糊,造成理解上的困扰。这个问题也部分导 致了主流语言一直缺乏对协程的支持。甚至在描述一些本质上属于协程的机制时,如Windows的纤程(Fiber),连协程这个术语都很少被提起。
直到2004年由Lua的作者Ana Lucia de Moura和Roberto Ierusalimschy所发表的论文Revisiting Coroutines中,才正式对协程进行了分类,论文中依照三个问题区分协程:
- 控制传递(Control-transfer)机制
- 协程是否作为语言的第一类(First-class)对象提供
- 协程是否为栈式(Stackful)构造,即是否可以在内部的嵌套调用中挂起
对称与非对称协程
控制传递机制的不同区分出了对称(Symmetric)和非对称(Asymmetric)协程。对称协程只提供一种传递操作,用于在协程间直接传递 控制。非对称协程(常称为半对称(Semi-symmetric)协程或半(Semi)协程)提供调用和挂起两种操作,挂起时控制返回给调用者。在我们的 生产-消费者模型的例子中,前者是对称协程,生成器是一种非对称协程。
出于支持并发而提供的协程通常是对称协程,用于表示独立的执行单元,如Modula-2中的协程。用于产生值序列的协程则为非对称协程,如迭代器和 生成器。在很长一段时间里的普遍看法是,对称与非对称协程的能力不同。所以一些支持通用协程机制的语言同时提供了这两类控制传递,如Simula和 BCPL。
事实上很容易证明这两种控制传递机制可以相互表达,因此要提供通用协程时只须实现其中一种即可。但是,两者表达力相同并不意味着在易用性上也相同。 对称协程会把程序的控制流变得复杂而难以理解和管理,而非对称协程的行为在某种意义上与函数类似,因为控制总是返回给调用者。使用非对称协程写出的程序更 加结构化。
第一类(First-class)与受限协程
协程是否作为语言的第一类对象提供对表达力的影响极大。为特定用途而实现的协程,往往把协程对象限制在 指定的代码结构中,无法由程序员直接控制。一些语言实现的迭代器(CLU,Sather)和生成器(Icon)被限制在某个循环内使用,属于受限协程。只有实现为第一类对象的协程可以提供自定义控制结构的能力,而这种能力正是协程强大的表现力所在。
栈式(Stackful)构造
栈式协程允许在内部的嵌套函数中挂起,恢复时从挂起点继续执行。非栈式协程只能在主体部分执行挂起操作,可以用来开发简单的迭代器或生成器,但遇到 复杂些的控制结构时,会把问题搞得更加复杂。例如,如果生成器的生成项是通过递归或辅助函数生成的,必须创建出一系列相应层级结构的辅助生成器连续生成项 直到到达原始调用点。非栈式协程也不足以实现用户级多任务。
完全协程
综上所述可以认为,是否为第一类对象以及是否为栈式构造,这两个问题决定了协程的能力。Revisiting Coroutines一文提出了完全协程的概念,即第一类、栈式的协程对象。随后论证了完全协程的表达力等同于One-shot continuation,关于Continuation的概念及相关论证在随后的文章中我会提到,Continuation的出现也一定程度上导致了对 协程研究的中止,因为普遍认为Continuation的表达力要远超协程。
如今对协程的研究和应用有重新复苏的趋势,主要集中在两个方向。一个是研究它在协作式多任务管理上相对于多线程的优势,目前以程序库和系统资源的方 式提供了一些此类协程。另一个就是用于迭代器和生成器的协程,如Perl、C#、Python等。而Lua基于Revisiting Coroutines的观点,实现了完全非对称协,事实也证明了这种机制在实现一些控制结构时异常方便和强大。
相关参考
关于协程的相关资料@sagasw有一篇相当全面的汇总,强烈推荐:关于线程Thread、协程Coroutine、生成器Generator、yield资料。
协程(二)协程的应用
上一篇中对协程的概念做出了解释和澄清。总的来说,完全协程才算得上是真正意义上的协程,其它如生成器等只是部分实现了协程概念的非完全协程,我们之后主要讨论完全协程。
本篇介绍一些协程的实际应用。协程本质是一种控制抽象,它的价值在于可以简洁优雅地实现一些控制行为。在协程中,控制可以从当前执行上下文跳转到程序的其它位置,并且可以在之后的任意时刻恢复当前执行上下文,控制从跳出点处继续执行。这种行为与Continuation类似,但协程相比之下更容易理解,某些情况下还更加高效。之后会有一篇专门对比这两个概念。
生产者-消费者模型
在前一篇中已有提及,这是协程最典型也最常见的应用场景。Conway提出协程这个概念时所解决的编译器问题就属于生产者-消费者问题。
管道(Pipeline)也是由生产者-消费者模型扩展而来的,管道实际上是由一个生产者,加上一个或多个过滤器(Filter),再加上一个最终的消费者组成的生产者-消费者链。其中过滤器既是生产者又是消费者。以下是由Lua实现的过滤器*:
1 function filter(ant)
2 return coroutine.wrap(function())
3 while true do
4 -- resume antecessor to obtain value
5 local x = ant()
6 -- yield transformed value
7 coroutine.yield(f(x))
8 end
9 end
10 end
只需要一行语句把生产者、过滤器和消费者串联起来就可以创建出一个管道:
consumer(filter(producer()))
生成器(Generator)
生成器实际上可以看作只有生产者的生产者-消费者模型。生成器的主要用途就是实现迭代器(Iterator)。需要提到的一点就是目前大多数语言提 供的生成器都是非栈式(Stackful)的,即不能在嵌套调用中直接yield,这样在实现复杂的生成器时会非常麻烦。比如要实现一个二叉树的先序遍 历,先看Lua实现*:
1 function preorder(node)
2 if node then
3 preorder(node.left)
4 coroutine.yield(node.key)
5 preorder(node.right)
6 end
7 end
8
9 -- create an iterator
10 function preorder_iterator(tree)
11 return coroutine.wrap(function()
12 preorder(tree)
13 return nil
14 end)
15 end
简洁而直接,因为Lua的协程是完全协程,允许在嵌套调用中yield。如果用C#实现:
1 public static class BinaryTree<T>
2 {
3 static IEnumerable<T> Preorder(Node<T> node)
4 {
5 if (node != null) {
6 foreach (var key in Preorder(node.left))
7 yield return key;
8
9 yield return node.key;
10
11 foreach (var key in Preorder(node.right))
12 yield return key;
13 }
14 }
15
16 public static IEnumerable<T> PreorderIterator(Node<T> tree)
17 {
18 foreach (var key in Preorder(tree))
19 yield return key;
20 }
21 }
可以看到,在递归的每一级都需要一个迭代块向上一级yield,直到最顶层。如果生成器更加复杂,比如需要调用一系列辅助方法,那么在每个调用点处都要增加机械的yield代码。
目标导向(Goal-oriented)编程
目标导向编程是指模式匹配 (Pattern-matching)或Prolog的查询这样的系统,由用户提出一个形式化定义的目标(Goal),系统会在一系列可选的子目标中寻找 直到确认一个解决方案。在寻找过程中常常会需要回溯(Backtracking)机制,这种机制使用完全非对称协程作为生成器很容易实现。
例如用Lua实现一个模式匹配系统,支持匹配常量、可选和序列三种模式的组合*:
1 -- matching a string literal
2 function prim(str)
3 return function(S, pos)
4 local len = string.len(str)
5 if string.sub(S, pos, pos+len-1) == str then
6 coroutine.yield(pos + len)
7 end
8 end
9 end
10
11 -- alternative patterns (disjunction)
12 function alt(patt1, patt2)
13 return function(S, pos)
14 patt1(S, pos)
15 patt2(S, pos)
16 end
17 end
18
19 -- sequence of sub-patterns (conjuction)
20 function seq(patt1, patt2)
21 return function(S, pos)
22 local btpoint = coroutine.wrap(function()
23 patt1(S, pos)
24 end)
25 for npos in btpoint do
26 patt2(S, npos)
27 end
28 end
29 end
30
31 function match(S, patt)
32 local len = string.len(S)
33 local m = coroutine.wrap(function() patt(S, 1) end)
34 for pos in m do
35 if pos == len+1 then
36 return true
37 end
38 end
39 return false
40 end
使用时形如("abc"|"de")."x"的目标可以定义如下:
patt = seq(alt(prim("abc"), prim("de")), prim("x"))
在进行匹配时这个目标被封装到一个协程中,通过循环逐一尝试匹配每个子目标。实现中的每个模式函数接受目标字符串和起始位置作为参数,匹配成功时 yield下一个位置给后续匹配,无法继续匹配时返回nil。我尝试了一下使用C#来实现上述代码,得到的编译错误是:不能在匿名方法或lambda表达 式内部使用yield语句。于是我便没有继续了,因为已经可以想象最后的完成代码会有多臃肿。这是C#生成器的又一使用限制,的确非常影响表达力。
协作式多任务(Cooperative multitasking)
并发编程所涉及的范围太广,这里仅讨论线程与协程的对比。在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其它协程 共享全局数据和其它资源。目前主流语言基本上都选择了多线程作为并发设施,与线程相关的概念是抢占式多任务(Preemptive multitasking),而与协程相关的是协作式多任务。
由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点),因此非常容易使用。下面是一段Lua实现多任务的代码*:
1 -- list of "live" tasks
2 tasks = {}
3
4 -- create a task
5 function create_task(f)
6 local co = coroutine.wrap(function() f(); return "ended" end)
7 table.insert(tasks, co)
8 end
9
10 -- task dispatcher
11 function dispatcher()
12 while true do
13 local n = table.getn(tasks)
14 if n == 0 then break end -- no more tasks to run
15 for i = 1, n do
16 local status = tasks[i]()
17 if status == "ended" then
18 table.remove(tasks, i)
19 break
20 end
21 end
22 end
23 end
协作式多任务的缺点之一是进行阻塞(Blocking)操作如IO时会阻塞掉整个程序,解决方案是提供一个辅助函数,初始化IO操作之后如果操作不能立即完成就挂起当前协程,Programming in Lua中给出了一个多任务下载的例子:
1 function download(host, file)
2 local c = assert(socket.connect(host, 80))
3 local count = 0
4 c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
5 while true do
6 local s, status, partial = receive(c)
7 count = count + #(s or partial)
8 if status == "closed" then break end
9 end
10 c:close()
11 print(file, count)
12 end
13
14 function receive(connection)
15 connection:settimeout(0)
16 local s, status, partial = connection:receive(2^10)
17 if status == "timeout" then
18 coroutine.yield(connection)
19 end
20 return s or partial, status
21 end
22
23 threads = {}
24
25 function get(host, file)
26 local co = coroutine.create(function()
27 download(host, file)
28 end)
29 table.insert(threads, co)
30 end
31
32 function dispatch()
33 local i = 1
34 while true do
35 if threads[i] == nil then
36 if threads[1] == nil then break end
37 i = 1
38 end
39 local status, res = coroutine.resume(threads[i])
40 if not res then
41 table.remove(threads, i)
42 else
43 i = i + 1
44 end
45 end
46 end
这个例子把每个下载任务放在一个协程中,通过settimeout(0)使获取操作不会阻塞,再由调度函数逐一唤醒协程执行,如果获取还未完成就挂起,直到所有下载完成。
协作式多任务的另一个缺点是无法利用多核资源,这一点我认为我们日常所编写的绝大部分应用都没有这个必要,除非是实时性要求非常高(如操作系统)或 需要尽可能地榨取机器性能(如Web服务器)的情况下(即使这时多线程也并非唯一选择)。所以在你打算使用多线程时先认真考虑一下是否协程就已经足够。在这篇采访中也有一些关于协程和并发的观点供参考。
异常处理
支持异常处理的语言需要实现两个基础原语:try和raise。try原语包含两项表达:主体和异常处理器。若主体正常返回,则返回值作为try的 值,忽略异常处理器。若主体遇到异常条件,则引发(raise)一个异常并立即送给异常处理器,主体的剩余部分被忽略。异常处理器可以返回一个值作为 try的值,或重新引发一个异常给更外层的异常处理器。
使用完全非对称协程来实现异常处理很简单:try原语由一个函数实现,此函数接受两个函数(主体和异常处理器)作为参数,然后在一个协程中执行主体函数;raise原语也是一个函数,在其中yield出一个异常即可。
总结
协程还适于实现状态机(每对入口/出口点表现一个状态),参与者模型(Actor model)(实质上也是协作式多任务)等。重要的是理解协程所表达的控制抽象,在此之上能够灵活运用的话,原本一些棘手的控制问题也许就可以简洁优雅地实现出来。
* 代码来自Revisiting Coroutines
协程(三)协程与Continuation
Continuation表示一个运算在其指定位置的剩余部分。 当Continuation作为语言的第一类(First-class)对象时,可用于实现多种控制结构。同样作为控制结构,First-class continuation的表达力比协程更加强大,而且有着明确定义的语义,以至于在它出现之后对协程的研究就几乎完全停止了。但后来Revisiting Coroutines中证明了完全协程与One-shot continuation的表达力是完全相同的,而且协程更容易理解和使用,在某些情况下也更加高效。
理解Continuation
Continuation是一种描述程序的控制状态的抽象,它用一个数据结构来表示一个执行到指定位置的计算过程;这个数据结构可以由程序语言访问 而不是隐藏在运行时环境中。Continuation在生成之后可作为控制结构使用,在调用时会从它所表示的控制点处恢复执行。
注意Continuation所保存的控制状态中并不包括数据。关于这一点有个很有趣的“Continuation三明治”的描述:
假设你在厨房里的冰箱前面,正打算做一个三明治。这时你获取一个Continuation放进兜里。然后从冰箱拿了些火鸡 和面包做了个三明治,放在了桌子上。现在调用兜里的那个Continuation,你会发现你又站在了冰箱前,正打算做个三明治。但这时桌子上已经有个三 明治了,而且火鸡和面包也不见了。于是你吃掉了三明治。
Continuation以及与相关的call/cc(Call-with-current-continuation)、CPS(Continuation-passing style)这几个概念比较难理解也容易混淆,要彻底把它们搞明白还真得花一番功夫。Continuation的资料也不多,这里列出几篇中文资料供参考:
- 从Continuation概念说到它在开发中的应用,这篇文章对Continuation的实现和CPS讲解得非常清楚。
- Continuations Made Simple and Illustrated,早年Python社区的讨论,有中文翻译简介延续“Continuation”。
- 尾递归与Continuation,老赵(@jeffz_cn)在C#中使用CPS的演示。
First-class continuation
Continuation这个术语很多时候也用于表示First-class continuation。这时它表示一种语言构造,使语言可以在任意点保存执行状态并且在之后的某个点返回。这里与协程做比较的正是First-class continuation(或者说是call/cc机制),而不是Continuation结构或CPS编程风格。
调用call/cc时,会把当前Continuation打包成一个第一类对象。然后这个捕获的Continuation被传给call/cc的参 数——此参数必须是带一个参数的过程。如果在这个过程中没有调用Continuation就返回了,则返回值作为call/cc的值。如果在其中调用了 Continuation并传给它一个值,则这个值立即返回到call/cc处。
基于First-class continuation很容易实现完全协程,主要思路是在切换协程时保存当前Continuation并调用目标协程的Continuation。以下是由Marc De Scheemaecker基于Ruby call/cc实现的完全对称协程:
1 class Coroutine
2 def initialize(&block)
3 # Creates a coroutine. The associated block does not run yet.
4 @started = false
5 @finished = false
6 @block = Proc::new {
7 callcc{|@cc|}
8 block.call
9 @finished = true
10 @started = false
11 }
12 end
13
14 def start
15 # Starts the block. It's an error to call this method on a coroutine
16 # that has already been started.
17 raise "Block already started" if @started
18
19 @started = true
20 @block.call
21 end
22
23 def switch(coroutine)
24 # Switches context to another coroutine. You need to call this method
25 # on the current coroutine.
26 switch = true
27
28 if not coroutine.finished? then
29 callcc{|@cc|}
30
31 if switch then
32 switch = false
33
34 if coroutine.running? then
35 coroutine.continuation.call
36 else
37 coroutine.start
38 end
39 end
40 end
41 end
42
43 def running?
44 # Returns true if the associated block is started and has not yet
45 # finished
46 @started and not @finished
47 end
48
49 def finished?
50 # Returns true if the associated block is finished
51 @finished
52 end
53
54 def continuation
55 # Returns the associated continuation object
56 @cc
57 end
58 end
One-shot continuation
所谓One-shot continuation,即只允许调用一次的Continuation。标准的Continuation是允许多次调用(Multi-shot)的,但 是很难高效地实现这样的Continuation,因为每次调用之前都必然要生成一个副本,而且在绝大多数情况下Continuation都只会被调用一 次,受此启发Bruggeman et al.提出了One-shot continuation的概念和控制符call/1cc。One-shot continuation几乎可以所有应用中替换标准Continuation(包括上面协程的实现)。多次调用One-shot continuation会引发错误,无论是隐式调用(从传给call/1cc的过程中返回)还是显式调用(直接调用由call/1cc创建的 Continuation)。
前面提到过完全协程与One-shot continuation的表达力是相同的,证明方式便是它们可以相互实现。从对称协程的视角来看,捕获一个One-shot continuation相当于新建一个协程并把控制传递给它。调用时相当于把控制返回给创建者。这种相似性使得基于对称协程可以很简洁地实现 call/1cc。
下面是Revisiting Coroutines中使用Lua实现One-shot continuation的代码,首先实现一个完全对称协程:
1 coro = {}
2 coro.main = function() end
3 coro.current = coro.main
4
5 -- function to create a new coroutine
6 function coro.create(f)
7 local co = function(val)
8 f(val)
9 error("coroutine ended")
10 end
11 return coroutine.wrap(co)
12 end
13
14 -- function to transfer control to a coroutine
15 function coro.transfer(co, val)
16 if coro.current == coro.main then
17 return coroutine.yield(co, val)
18 end
19
20 -- dispatching loop
21 while true do
22 coro.current = co
23 if co == coro.main then
24 return val
25 end
26 co, val = co(val)
27 end
28 end
然后是call/1cc:
1 function call1cc(f)
2 -- save the continuation "creator"
3 local ccoro = coro.current
4 -- invoking the continuation transfers control
5 -- back to its creator
6 local cont = function(val)
7 if ccoro == nil then
8 error("one shot continuation called twice")
9 end
10 coro.transfer(ccoro, val)
11 end
12 -- when a continuation is captured,
13 -- a new coroutine is created and dispatched
14 local val
15 val = coro.transfer(coro.create(function()
16 local v = f(cont)
17 cont(v)
18 end))
19 -- when control is transfered back, the continuation
20 -- was "shot" and must be invalidated
21 ccoro = nil
22 -- the value passed to the continuation
23 -- is the return value of call1/cc
24 return val
25 end
效率问题
可以看到,Continuation可以实现协程,同时协程也可以实现One-shot continuation,但这两种相反实现的效率并不相同。
在Bruggeman et al.描述的One-shot continuation实现中,控制栈由栈段(Stack segment)组成的链表表示,整个控制栈被构造成栈帧(Stack of frame)或活动记录(Activation record)。捕获Continuation时,当前栈段被保存到Continuation中,然后分配一个新的栈段。调用Continuation 时,丢弃当前栈段,控制返回到之前保存的栈段。
创建一个协程同样包括分配一个单独的栈,但挂起和恢复协程的代价只比标准的函数调用略高。
使用协程实现One-shot continuation时,创建一个单独的协程——即栈“段”——就足以表示一个Continuation。因此,通过协程实现的One-shot continuation与直接实现效率几乎相同。而以Continuation实现协程时,通常每次协程挂起时都需要捕获一个新的 Continuation。这导致每次控制转换都需要重新分配一个栈段,相比直接实现的协程效率要大大降低并且需要更多内存。
这里有一篇Lua、LuaJIT Coroutine和Ruby Fiber的切换效率对比,我猜测大概就是因为Ruby是在call/cc之上实现的Fiber。
[转载]协程-cooperative multitasking的更多相关文章
- 转载:PHP 协程实现
转自:https://newt0n.github.io/2017/02/10/PHP-%E5%8D%8F%E7%A8%8B%E5%8E%9F%E7%90%86/ 实现 PHP 协程需要了解的基本内容. ...
- Unity协程(Coroutine)原理深入剖析(转载)
记得去年6月份刚开始实习的时候,当时要我写网络层的结构,用到了协程,当时有点懵,完全不知道Unity协程的执行机制是怎么样的,只是知道函数的返回值是IEnumerator类型,函数中使用yield r ...
- [转载] Python协程从零开始到放弃
Python协程从零开始到放弃 Web安全 作者:美丽联合安全MLSRC 2017-10-09 3,973 Author: lightless@Meili-inc Date: 2017100 ...
- [转载]Python 3.5 协程究竟是个啥
http://blog.rainy.im/2016/03/10/how-the-heck-does-async-await-work-in-python-3-5/ [译] Python 3.5 协程究 ...
- 进程、线程、轻量级进程、协程与 go 的 goroutine【转载+整理】
本文内容 进程 线程 协程 Go 中的 goroutine 参考资料 最近,看一些文章,提到"协程"的概念,心想,进程,线程,协程,前两个很容易,任何一本关于操作系统的书都有说,开 ...
- python进程、线程、协程(转载)
python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资 ...
- 分析Tornado的协程实现
转自:http://www.binss.me/blog/analyse-the-implement-of-coroutine-in-tornado/ 什么是协程 以下是Wiki的定义: Corouti ...
- (zt)Lua的多任务机制——协程(coroutine)
原帖:http://blog.csdn.net/soloist/article/details/329381 并发是现实世界的本质特征,而聪明的计算机科学家用来模拟并发的技术手段便是多任务机制.大致上 ...
- Lua学习笔记(六):协程
多线程和协程 多线程是抢占式多任务(preemptive multitasking),每个子线程由操作系统来决定何时执行,由于执行时间不可预知所以多线程需要使用同步技术来避免某些问题.在单核计算机中, ...
随机推荐
- 使用node+vue.js实现SPA应用,nodevue.jsspa应用
使用node+vue.js实现SPA应用,nodevue.jsspa应用 http://www.bkjia.com/Javascript/1097617.html https://github.com ...
- 【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
前言 NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能.这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2.而Netty的主要版本是Netty3和Netty ...
- python 反射的使用
反射这个功能在很多编程语言中都有,在Python中自然也不例外.其实编程语言中的很多功能都能用简单的代码来验证. 在code代码之前,先简单的了解下反射的几个属性. hasattr(obj,name_ ...
- mysql数据库移植
在mysql数据库移植的时候,把自己电脑上mysql中data目录的一些重要文件复制到其他电脑上,先备份一下其他电脑上的mysql的data目录,然后替换! 例如我的mysql默认的数据库文件位置: ...
- H5游戏开发之Stick Hero
自从上次发布一个小恐龙游戏以后,到现在10天了,前后又写了3个游戏,挑了一个感觉比较有挑战的游戏和大家分享一下. 效果演示 这是我模拟一个苹果游戏<stick hero>游戏写的一个小游戏 ...
- android 中resources管理
主要存在于res/value文件夹中 定义: dimen.xml:主要用于设置像素默认值 <resources> res/values/dimens.xml <dimen name= ...
- android调试输出
测试时不想直接debug总要调试输出一些字符串信息,那以下方法可选: 1.用Log.i(TAG, "onCreate");日志输出. 先要引用 import android.uti ...
- Halcon与MFC交互编程
Halcon是商业化的机器视觉软件.网上下了halcon10的破解版,安装后编写了个图像显示的MFC小程序. 编译器用的是VS2008. 1 配置halcon环境 新建为MFC后,在VC++目录中配置 ...
- iframe的内容增高或缩减时设置其iframe的高度的处理方案
WEB管理软件往往是如下结构的 用户点击子页tab切换中部的显示内容,在切换过程中需要保证前面的子页保持先前的状态.这种情况一般都使用iframe来来作为切换的子页显示内容. 但是这里有一个问题,if ...
- WCF实现方法重载
一.服务契约(包括回调契约)通过指定不同的OperationContract.Name来实现重载方法,当然代码部份还是必需符合C#的重载要求,即相同方法名称,不同的参数个数或参数类型 namespac ...