1. Introduction to Lua coroutine
Coroutine (coroutine), which is a collaboration routine, was first proposed and implemented by Melvin Conway in 1963. Unlike threads in mainstream programming languages, threads are intrusive components. The thread-implemented system is called a preemptible multi-task system, while the multi-task system implemented by coroutine becomes a collaborative multi-task system. Due to the lack of yield semantics, scheduling, sleep suspension, context switching, and other system overhead are inevitable during the running process. You also need to be careful to use the synchronization mechanism to ensure the normal operation of multithreading. The coroutine running command series are fixed and do not require a synchronization mechanism. switching between coroutines only involves the exchange of control. Compared with the thread, it is very lightweight. However, multiple threads can run at the same time, but only one route can run.
Coroutine has two important features:
1. Private data remains valid during the continuous operation of the coroutine
2. The coroutine gives control after each yield operation and continues execution from the stop point after being resume.
In layman's terms, it is more like a function with static data and multiple entry points and return points. The following is a simple example to look at this nature:
co = coroutine.create(function(a) print("a = "..a) c = coroutine.yield(a+1) print("c = "..c) return c*2end)_, b = coroutine.resume(co, 1)print("b = "..b)_, d = coroutine.resume(co, b )print("d = "..d)
Running result:
A = 1
B = 2
C = 2
D = 4
In addition to the entry point (function entrance) and return point (function end) of a common function, coroutine Co also has an entry point and return point in yield. When the main program resume for the first time, it passes 1 to parameter A. When it runs to yield, the coroutine returns control to the main program and provides the return value through the yield parameter. Therefore, B = a + 1, resume again, the main program passes the data to the CO variable C through parameter B, so as to enter the function from the second entry point, and finally return the value C * 2 to D.
The coroutine of resume/yield semantic implementation is an asymmetric coroutine. In an asymmetric coroutine, the relationship between the caller and the called is fixed. The caller transfers the control to the called through resume, the called user can only return to the caller through yield, but not to other coroutines. For example, a resume B resume C and C yield can only be a but not a or other coroutine.
Everything in the world is opposite and unified. Since there is an asymmetric coroutine, of course there is a symmetric coroutine. The symmetric coroutine has only one semantics that can directly transfer the control flow to the target coroutine.
The expression capability of asymmetric coroutine and symmetric coroutine is the same. Lua only implements asymmetric coroutine. One important reason is that Lua is implemented by C, the call and called relationship of asymmetric coroutine is very similar to that of function call of C, which facilitates extension programming of Lua and C.
Ii. coroutine practice
The following example uses coroutine to implement a classic producer-consumer model.
count = 10productor = coroutine.create( function () i = 0 while(true) do i = i+1 coroutine.yield(i) end end ) consumer = coroutine.create( function(co) n = 1 while(n<count) do _, v = coroutine.resume(co) print(v) n = n+1 end end ) coroutine.resume(consumer, productor)
Consumer starts productor. After productor generates an item, it returns it to consumer through yield, and then suspends it to wait for consumer to resume next time, which is very simple and intuitive.
Another important role of coroutine is to serve as an iterator that accesses data structures in turn. The following code shows how to access a binary tree in sequence.
l = { v = 1, left = nil, right = nil, } r = { v = 2, left = nil, right = nil, } root = { v = 3, left = l, right = r, } preorder = function(root) if(root == nil) then return end coroutine.yield(root.v) preorder(root.left) preorder(root.right) end preco = coroutine.create(preorder) view = function(co, root) state, v = coroutine.resume(co, root) if(state == false) then return end print(v) while(true) do state, v = coroutine.resume(co) if(state == false or v == nil) then return end print(v) end end view(preco, root)
For coroutine, the implementation principle of a very lightweight coroutine is introduced here. Yunfeng uses uconext to implement a coroutine library similar to Lua. If you are interested, you can study it.
Reference:
Coroutines in C
Introduction to Lua programming coroutine