YouTip LogoYouTip

Lua Coroutine

* * * ## What is a Coroutine? Lua coroutines are similar to threads: they have their own independent stacks, local variables, and instruction pointers, while sharing global variables and most other things with other coroutines. A coroutine can be understood as a special kind of thread that can pause and resume its execution, thereby allowing non-preemptive multitasking. Coroutines are a very powerful feature, but they can also be complex to use. ## Basic Syntax Coroutines are supported by the `coroutine` module. To use coroutines, you can create a new coroutine object within a function using **`coroutine.create`** and start its execution using **`coroutine.resume`**. A coroutine can actively pause its own execution by calling **`coroutine.yield`** and return control to the caller. | Method | Description | | --- | --- | | `coroutine.create()` | Creates a coroutine, returns the coroutine. The argument is a function. When used with `resume`, it wakes up the function call. | | `coroutine.resume()` | Restarts a coroutine. Used in conjunction with `create`. | | `coroutine.yield()` | Suspends a coroutine, setting it to a suspended state. When used with `resume`, it can have many useful effects. | | `coroutine.status()` | Checks the status of a coroutine. Note: A coroutine has three states: dead, suspended, running. Refer to the program below for details on when these states occur. | | `coroutine.wrap()` | Creates a coroutine, returns a function. Once you call this function, you enter the coroutine. Its functionality overlaps with `create`. | | `coroutine.running()` | Returns the currently running coroutine. A coroutine is a thread. When using `running`, it returns the thread ID of a coroutine. | The following example demonstrates how to use Lua coroutines: ## Example ```lua function foo() print("Coroutine foo starts executing") local value = coroutine.yield("Suspend foo's execution") print("Coroutine foo resumes execution, passed value: " .. tostring(value)) print("Coroutine foo finishes executing") end -- Create a coroutine local co = coroutine.create(foo) -- Start the coroutine local status, result = coroutine.resume(co) print(result) -- Output: Suspend foo's execution -- Resume the coroutine's execution, passing a value status, result = coroutine.resume(co, 42) print(result) -- Output: Coroutine foo resumes execution, passed value: 42 In the example above, we defined a function named `foo` as a coroutine. Inside the function, we used `coroutine.yield` to pause the coroutine's execution and return a value. In the main program, we used `coroutine.create` to create a coroutine object and `coroutine.resume` to start its execution. After the first call to `coroutine.resume`, the coroutine executes until it reaches `coroutine.yield`, pauses, and returns the value to the main program. Then, we call `coroutine.resume` again, passing a value as an argument for when the coroutine resumes execution. Executing the above code produces the following output: Coroutine foo starts executing Suspend foo's execution Coroutine foo resumes execution, passed value: 42 Coroutine foo finishes executing nil It's important to note that the state of a coroutine can be obtained using the `coroutine.status` function. By checking the state, you can determine the execution status of the coroutine (e.g., running, suspended, dead, etc.). ### The following example demonstrates the usage of each method: ## coroutine_test.lua file ```lua -- coroutine_test.lua file -- Creates a new coroutine object `co`, where the coroutine function prints the passed argument `i` co = coroutine.create( function(i) print(i); end ) -- Uses `coroutine.resume` to start the execution of coroutine `co`, passing argument 1. The coroutine starts executing, printing output 1. coroutine.resume(co, 1) -- 1 -- Checks the status of coroutine `co` using `coroutine.status`, output is 'dead', indicating the coroutine has finished executing. print(coroutine.status(co)) -- dead print("----------") -- Uses `coroutine.wrap` to create a coroutine wrapper, converting the coroutine function into a directly callable function object. co = coroutine.wrap( function(i) print(i); end ) co(1) print("----------") -- Creates another coroutine object `co2`, where the coroutine function prints numbers 1 to 10 in a loop. When the loop reaches 3, it prints the current coroutine's status and the running thread. co2 = coroutine.create( function() for i=1, 10 do print(i) if i == 3 then print(coroutine.status(co2)) -- running print(coroutine.running()) -- thread: XXXXXX end coroutine.yield() end end ) -- Continuously calls `coroutine.resume` to start the execution of coroutine `co2`. coroutine.resume(co2) -- 1 coroutine.resume(co2) -- 2 coroutine.resume(co2) -- 3 -- Checks the status of coroutine `co2` using `coroutine.status`, output is 'suspended', indicating the coroutine is paused. print(coroutine.status(co2)) -- suspended print(coroutine.running()) print("----------") The output of executing the above example is: 1 dead ---------- 1 ---------- 1 2 3 running thread: 0x7fb801c05868 false suspended thread: 0x7fb801c04c88 true ---------- From `coroutine.running`, you can see that a coroutine is essentially a thread at the underlying implementation level. When you create a coroutine, you are registering an event in a new thread. When you use `resume` to trigger the event, the coroutine function created is executed. When it encounters `yield`, it means the current thread is suspended, waiting for another `resume` to trigger the event. Next, let's analyze a more detailed example: ## Example ```lua function foo(a) print("foo function outputs", a) return coroutine.yield(2 * a) -- Returns the value of 2*a end co = coroutine.create(function(a, b) print("First coroutine execution output", a, b) -- co-body 1 10 local r = foo(a + 1) print("Second coroutine execution output", r) local r, s = coroutine.yield(a + b, a - b) -- a, b are the values passed during the first coroutine call print("Third coroutine execution output", r, s) return b, "End coroutine" -- b is the value passed during the second coroutine call end) print("main", coroutine.resume(co, 1, 10)) -- true, 4 print("--separator----") print("main", coroutine.resume(co, "r")) -- true 11 -9 print("---separator---") print("main", coroutine.resume(co, "x", "y")) -- true 10 end print("---separator---") print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine print("---separator---") The output of executing the above example is: First coroutine execution output 1 10 foo function outputs 2 main true 4 --separator---- Second coroutine execution output r main true 11 -9 ---separator--- Third coroutine execution output x y main true 10 end ---separator--- main false cannot resume dead coroutine ---separator--- The above example is explained as follows: * Calling `resume` wakes up the coroutine. If the `resume` operation is successful, it returns `true`; otherwise, it returns `false`. * The coroutine runs. * It runs to the `yield` statement. * `yield` suspends the coroutine, and the first `resume` returns. (Note: Here, `yield` returns, and its parameters are the arguments of `resume`). * The second `resume` wakes up the coroutine again. (Note: Here, the parameters of `resume`, except the first one, become the arguments for `yield`). * `yield` returns. * The coroutine continues to run. * If you continue to call the `resume` method after the used coroutine has finished running, it will output: `cannot resume dead coroutine`. The power of the cooperation between `resume` and `yield` lies in that `resume` is in the main thread, passing external state (data) into the coroutine; while `yield` returns internal state (data) back to the main thread. * * * ## Producer-Consumer Problem Now I will use Lua coroutines to solve the classic producer-consumer problem. ## Example ```lua local newProductor function productor() local i = 0 while true do i = i + 1 send(i) -- Send the produced item to the consumer end end function consumer() while true do local i = receive() -- Get an item from the producer print(i) end end function receive() local status, value = coroutine.resume(newProductor) return value end function send(x) coroutine.yield(x) -- x represents the value to be sent. After the value is returned, the coroutine is suspended. end -- Start the program newProductor = coroutine.create(productor) consumer() The output of executing the above example is: 1 2 3 4 5 6 7 8 9 10 11 12 13 …… * * * ## Differences Between Threads and Coroutines The main difference between threads and coroutines is that a program with multiple threads can run several threads simultaneously, while coroutines need to run in a cooperative manner. At any given moment, only one coroutine is running, and this running coroutine is only suspended when explicitly requested to do so. Coroutines are somewhat similar to synchronous multithreading, where several threads waiting for the same lock resemble coroutines. The main differences are summarized as follows: * **Scheduling Method:** Threads are typically preemptively scheduled by the operating system's scheduler, which switches execution rights between different coroutines. Coroutines, on the other hand, are cooperatively scheduled, and the transfer of execution rights is explicitly controlled by the programmer. * **Concurrency:** Threads execute concurrently; multiple threads can run simultaneously on multiple processor cores or switch execution on a single core via time-slicing. Coroutines are cooperative; only one coroutine is in a running state at a time, and other coroutines must wait for the current coroutine to voluntarily give up execution rights. * **Memory Usage:** Threads usually require independent stacks and context environments, so creating and destroying threads incurs additional overhead. Coroutines can share the same stack and context, so the overhead of creating and destroying coroutines is smaller. * **Data Sharing:** Threads can share memory space, but thread safety and synchronization issues need to be considered. Coroutines typically share data through parameter passing and return values, providing better data isolation between different coroutines. * **Debugging and Error Handling:** Threads are generally more complex to debug and handle errors because the interaction and concurrent execution between multiple threads can lead to difficult-to-debug issues. Coroutines are relatively simpler to debug and handle errors because their execution flow is explicitly controlled by the programmer. Overall, threads are suitable for scenarios requiring concurrent execution, such as leveraging parallelism on multi-core processors to speed up task execution. Coroutines are suitable for scenarios requiring cooperation and coordination, such as state machines, event-driven programming, or cooperative task processing. The choice between using threads or coroutines depends on the specific application requirements and programming model.
← Cpp Standard LibraryCpp Web Programming β†’