What is a coroutine? How are they related to concurrency?
Coroutines and concurrency are largely orthogonal. Coroutines are a general control structure whereby flow control is cooperatively passed between two different routines without returning.
The 'yield' statement in Python is a good example. It creates a coroutine. When the 'yield ' is encountered the current state of the function is saved and control is returned to the calling function. The calling function can then transfer execution back to the yielding function and its state will be restored to the point where the 'yield' was encountered and execution will continue.
I find most of the answers too technical even though it is a technical question. I had a hard time trying to understand the coroutine process. I kind of get it but then I don't get it at the same time.
I found this answer here very helpful:
https://dev.to/thibmaek/explain-coroutines-like-im-five-2d9
To quote from Idan Arye:
To build on your story, I'd put it something like this:
You start watching the cartoon, but it's the intro. Instead of watching the intro you switch to the game and enter the online lobby - but it needs 3 players and only you and your sister are in it. Instead of waiting for another player to join you switch to your homework, and answer the first question. The second question has a link to a YouTube video you need to watch. You open it - and it starts loading. Instead of waiting for it to load, you switch back to the cartoon. The intro is over, so you can watch. Now there are commercials - but meanwhile a third player has joined so you switch to the game And so on...
The idea is that you don't just switch the tasks really fast to make it look like you are doing everything at once. You utilize the time you are waiting for something to happen(IO) to do other things that do require your direct attention.
Definitely check the link, there are much more that I cannot quote everything.
I find an explanation from this link is pretty straight forward. None of those answers try to explain concurrency vs parallelism except the last bullet point in this answer.
cited from "programming Erlang", by Joe Armstrong, the legendary:
a concurrent program can run potentially faster on a parallel computer.
a concurrent program is a program written in a concurrent programming language. We write concurrent programs for reasons of performance, scalability, or fault tolerance.
a concurrent programming language is a language that has explicit language constructs for writing concurrent programs. These constructs are an integral part of programming language and behave the same way on all operating systems.
a parallel computer is a computer that has several processing units (CPUs or cores) that can run at the same time.
So concurrency is not the same as parallelism. You can still write concurrent programs on a single-core computer. The time-sharing scheduler will make you feel your program is running concurrently.
The concurrent program has the potential to run in parallel in a parallel computer but not guaranteed. OS may only give you one core to run your program.
Therefore, concurrency is a software model from a concurrent program that doesn't mean your program can run in parallel physically.
The word “coroutine” is composed of two words: “co” (cooperative) and “routines” (functions).
a. does it achieve concurrency or parallelism?
To be simple, let's discuss it on a single-core computer.
Concurrency is achieved by time-shares from OS. A thread executes its code in its assigned time frames on the CPU core. It can be preempted by OS. It may also yield control to OS.
A coroutine, on the other hand, yields control to another coroutine within the thread, not to OS. So all coroutines within a thread still exploit the time frame for that thread without yielding the CPU core to other threads managed by OS.
Therefore, you can think of coroutine achieves time-shares by the user not by OS (or quasi-parallelism). Coroutines run on the same core assigned to the thread that runs those coroutines.
Does Coroutine achieve parallelism? If it's CPU-bound code, no. Like time-shares, it makes you feel they run in parallel but their executions are interleaved not overlapped. If it's IO-bound, yes, it achieves parallel by hardware (IO devices) not by your code.
b. the difference with function call?
As the pic shows, it doesn't need to call return to switch control. It can yield without return. A coroutine saves and shares state on the current function frame (stack). So it's much more lightweight than function since you don't have to save registers and local variables to stack and rewind call stack when call ret.
Basically, there are two types of Coroutines:
Kotlin implements stackless coroutines — it’s mean that the coroutines don’t have own stack, so they don’t map on native thread.
These are the functions to start the coroutine:
launch{}
async{}
You can learn more from here :
https://www.kotlindevelopment.com/deep-dive-coroutines/
https://blog.mindorks.com/what-are-coroutines-in-kotlin-bf4fecd476e9
A coroutine is a special kind of subprogram. Rather than the master- slave relationship between a caller and a called subprogram that exists with conventional subprograms, caller and called coroutines are more equitable.
A coroutine is a subprogram that has multiple entries and controls them itself – supported directly in Lua
Also called symmetric control: caller and called coroutines are on a more equal basis
A coroutine call is named a resume
The first resume of a coroutine is to its beginning, but subsequent calls enter at the point just after the last executed statement in the coroutine
Coroutines repeatedly resume each other, possibly forever
Coroutines provide quasi-concurrent execution of program units (the coroutines); their execution is interleaved, but not overlapped
A coroutine is a single-threaded solution to achieve concurrency.
A-Start ------------------------------------------ A-End
| B-Start -----------------------------------------|--- B-End
| | C-Start ------------------- C-End | |
| | | | | |
V V V V V V
1 thread->|<-A-|<--B---|<-C-|-A-|-C-|--A--|-B-|--C-->|---A---->|--B-->|
In comparison to a multithreading solution:
thread A->|<--A| |--A-->|
thread B------>|<--B| |--B-->|
thread C ---------->|<---C| |C--->|
If you are still confused, here is a very simple way of understanding a co-routine. First off, what is a routine? In a lay man's term, a routine is something that we do again and again (for example, your morning routine). Similarly. in programming languages, a routine is a piece of code that we use again and again, e.g., a function. Now, if you look at the general characteristic of a function or routine (note: I am cautiously using these two terms interchangeably), it takes some inputs and hogs the CPU threads for as long as the function needs to output the result. Meaning, functions or routines are blocking calls in you code. However, a co-routine is a special kind of routine that can co-exist (the "co" part of the word co-routine comes from this) with other routines at the same time and we can make this happen in programming languages with the help of asynchronous programming. In Asynchronous programming, when one co-routine is waiting for something to happen (e.g., disk io), the other co-routine will start working and when this co-routine is in a waiting state the other co-routine will be active ultimately reducing the waiting time of our code.
If you understand the above, let's see how you can create a co-routine function in Python. You can define a co-routine function as following -
async def my_coroutine_function():
return 123
And you can the call the above co-routine by adding await in front of the co-routine-
my_result = await my_coroutine_function()
To conclude,
When you're watching a TV show and as soon as the Ad comes, you take your phone and text a friend - what you have just done is Asynchronous Programming. While your TV show (a co-routine) was on a waiting state, you went ahead and made your other co-routine (texting your friend) active.
From Python Coroutine:
Execution of Python coroutines can be suspended and resumed at many points (see coroutine). Inside the body of a coroutine function, await and async identifiers become reserved keywords; await expressions, async for and async with can only be used in coroutine function bodies.
From Coroutines (C++20)
A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller. This allows for sequential code that executes asynchronously (e.g. to handle non-blocking I/O without explicit callbacks), and also supports algorithms on lazy-computed infinite sequences and other uses.
Compare with other's answer:
In my opinion, the resumed later part is a core difference, just like @Twinkle's.
Although many fields of the document are still work in progress, however, this part is similar to most answer, except @Nan Xiao 's
Coroutines, on the other hand, are collaborative: at any given time, a program with coroutines is running only one of its coroutines, and this running coroutine suspends its execution only when it explicitly requests to be suspended.
Since it's quoted from Program in Lua, maybe it's language related(not familiar with Lua currently), not all document mentioned the only one part.
The relation with concurrent:
There is an "Execution" part of the Coroutines (C++20).Too long to quote here.
Besides the detail, there are several states.
When a coroutine begins execution
When a coroutine reaches a suspension point
When a coroutine reaches the co_return statement
If the coroutine ends with an uncaught exception
When the coroutine state is destroyed either because it terminated via co_return or uncaught exception, or because it was destroyed via its handle
as the comment from @Adam Arold under @user217714's answer. It's concurrency.
But it's different from multithreading.
from std::thread
Threads allow multiple functions to execute concurrently. Threads begin execution immediately upon construction of the associated thread object (pending any OS scheduling delays), starting at the top-level function provided as a constructor argument. The return value of the top-level function is ignored and if it terminates by throwing an exception, std::terminate is called. The top-level function may communicate its return value or an exception to the caller via std::promise or by modifying shared variables (which may require synchronization, see std::mutex and std::atomic)
Since it's concurrency, it works like multithreading especially when waiting is unavoidable(from the OS perspective), that's also why it's confusing.
I will expand on @user21714 's answer. Coroutines are independent paths of execution that can not run simultaneously. They depend upon a controller - for example a python controller library - to handle switching between these paths. But for this to work the coroutines themselves need to invoke yield or similar structures that allow their execution to be paused.
Threads instead are running on independent compute resources and in parallel with each other. Since they are on different resources there is no need for invoking yield to allow the other paths of execution to proceed.
You can see this effect by starting a multihreaded program - e.g. a jvm application - in which all eight of your core i7 hyperthread cores are utilized: you might see 797% utilization in Activity Monitor or Top. Instead when running a typical python program - even one with coroutines or python threading - the utilization will max out at 100%. I.e. one machine hyperthread.
Usually we heart something like - coroutines are light weight threads, they allow us to write asynchronous, non-blocking code in a synchronous manner
As for Kotlin Coroutines:
Coroutine is a synthetic sugar/additional layer which allows you to run a big task in a non-blocking way and without callbacks. Coroutine consists of some classes(Job, Dispatcher, Scope, Builder) and body
Lets review some example
suspend fun downloadFile(): File {
//logic
}
suspend fun saveFile(file: File) {
//logic
}
GlobalScope.launch {
val downloadResult = downloadFile() //suspend function
show(downloadResult) //UI
saveFile(downloadResult) //suspend function
}
It creates Continuation class which is state machine with invokeSuspend() function
class Continuation {
File file;
void invokeSuspend(Object result) {
switch (label) {
case 0: {
label = 1;
downloadFile(this); //suspend function
return;
}
case 1: {
file = (File) result;
show(file); //UI
saveFile(file, this); //suspend function
return;
}
}
}
}
suspended
Continuation - passes continuation into the functionContinuation.resume() -> Continuation.invokeSuspend()The main point that behavior of coroutine completely depends on library realisation