1

People always show the same example when I read articles about concurrency in kotlin coroutine or golang goroutine.

Create 100_000 Threads in Java or C#, ooopps Stackoverflow.

Yes. but anyone Who uses directly Thread classes in Java or C#?

In java and C#, There are thread pools for CompletableFuture and Task.

When We try to create 100_000 Task or CompletableFuture, We can do that easily with ExecuterService/ForkJoinPool or dotnet DefaultThread Pool. They will reuse the threads. If there is no available thread. Tasks will wait in the queue.

My Questions;

  1. yes structured concurrency is good for cancellations. But Kotlin uses the Thread Pool like CompletableFuture. But unlike Java Callbacks, It provides natural code syntax. The only Difference is Syntax for Kotlin coroutine between c# Task or Java CompletableFuture?

  2. Kotlin runs on JVM. as far as I know, JVM doesn't support green Threads. But people talk like kotlin uses Green Threads. How is that possible with JVM? And Why Coroutines are called Lightweight Threads. Then We can say CompletableFuture and Task are Lightweight Thread too. Right?

Yes, golang has a scheduler. goroutines are user-level threads. When we create a goroutine it goes to localrunqueue. And a dedicated OS thread gets goroutines one by one from that queue and executes. There are no context switch operations. All of them run on the same OS Thread until blocking. Goroutines are cheap and We can say that YES goroutines are Lightweight Threads.

Maybe I'm completely wrong about coroutines. please correct me.

eren arslan
  • 197
  • 2
  • 11
  • You are correct: coroutines are basically the same thing as completable futures, just with a much simpler and more straightforward syntax. There's not necessarily that much difference between that and green threads. – Louis Wasserman Jul 15 '22 at 21:44
  • Last paragraph is correct, except it is not one OS Thread. Goroutines are executed on multiple threads if necessary - https://stackoverflow.com/a/39246575/1420332 – Dmitry Harnitski Jul 16 '22 at 01:21
  • yes, I know. I focused only for one logical processer – eren arslan Jul 16 '22 at 02:22

1 Answers1

0

Making things simple:

  1. Thread - easy to use, because it is sequential. Reading from the network and writing to a file is as simple as: writeToDisk(readFromNetwork()). On the other hand, it is expensive to run each task in a separate thread.
  2. Executors/CompletableFuture - more performant, it makes better use of both CPU and memory. On the other hand, it requires using callbacks and the code quickly becomes hard to read and maintain.
  3. Coroutines - both sequential and performant.

I ignore other features of coroutines like structured concurrency, because this was not your main concern.

broot
  • 21,588
  • 3
  • 30
  • 35
  • So as I said the only difference is syntax. But Why do people call it lightweight? – eren arslan Jul 15 '22 at 22:24
  • Yes, it is a difference in syntax if you compare to 2., but it is a difference in performance if you compare to 1. This is why they are called "lightweight threads". They work like threads, but are not heavy. – broot Jul 15 '22 at 22:29
  • How is that possible? what do you mean "They work like threads, but are not heavy." like goroutines? – eren arslan Jul 15 '22 at 22:34
  • I mean they are like threads, because they are sequential by nature. Imagine you work with actor model or some other code where you have several components that exchange messages between each other. Each of them is some kind of an infinite loop where it reads data from its input(s) and writes to its output(s). Normally, you would spawn a separate thread per each component, so it can stay in a loop forever. 1000 components - 1000 threads. With coroutines you spawn 1000 coroutines and the code is very similar to the previous one, but internally it runs on e.g. 8 threads. – broot Jul 15 '22 at 22:50
  • So this is why I say they are like threads. You spawn them, they run some code, potentially indefinitely, if they have to wait for something, they just wait in-line, etc. Implementing the same with CompletableFuture is a much different story, you need to do it in a much different way. So from the end user perspective coroutines are functionally very similar to threads. From the technical perspective, they work in a very similar way to executors and futures. – broot Jul 15 '22 at 22:55
  • Yes. But With 8 thread size Executer service, I can execute 1000 CompletableFuture with 8 threads. This is the same. the only difference is syntax again. – eren arslan Jul 15 '22 at 22:59
  • You will submit 1000 tasks that run infinite loops, first 8 will start executing and the rest will never even start executing, they will wait in the queue forever. Or another example: what will happen if first 8 tasks will have to wait 30s for something, e.g. network or disk? – broot Jul 15 '22 at 23:02
  • even in golang system calls (except http) blocks the thread. Do you mean coroutines don't block worker threads? and yes CompletableFuture doesn't block the main thread too. only worker. If you use event-driven (non-blocking) drivers. it doesn't block like r2dbc or reactive Cocuhbase etc. – eren arslan Jul 15 '22 at 23:03
  • btw I can provide this with executorservice cached thread pool. and golang is almost doing same thing. When you blocked. Create new thread and and continue . when thread is idle destroy it. – eren arslan Jul 15 '22 at 23:05
  • Yes, coroutines can wait in the current line of code without blocking the thread. This is really their main feature. – broot Jul 15 '22 at 23:06
  • yes but this time, coroutine blocks the worker threads like CompletableFuture. CompletableFuture doesn't block the main thread too. – eren arslan Jul 15 '22 at 23:09
  • No, it doesn't block any thread. Main, worker, none of them. – broot Jul 15 '22 at 23:10
  • if the driver doesn't provide it. how is that possible? For example Reactive drivers like r2dbc, HttpClient, etc. They support it and yes as You said They don't block any thread. But if We use it with blocking resources? – eren arslan Jul 15 '22 at 23:13
  • Well, it has to block some thread if doing blocking IO, because this is a limitation from the operating system. But it doesn't block any thread if waiting on some timer, waiting on another coroutine, etc. All synchronization utils like mutexes, channels (equivalent of blocking queues), etc. do not block threads. And even in the case of blocking IO, it is delegated to a separate thread (similarly as in other frameworks), but at least the code that invoked IO operation still can wait in-line, even if it was running inside the main thread. It won't block it. – broot Jul 15 '22 at 23:18
  • So for example for the UI application you can write code like this: `val query = searchField.getValue(); val results = fetchDataFromServer(query); updateResultsView(results)`. You can execute it in the main thread and you won't block it. It will require to block some thread in the second line, yes, but the main thread won't be blocked. And I don't know golang, but I don't see a reason why it can't do the same. Kotlin isn't the only language with coroutines support. – broot Jul 15 '22 at 23:27