Why 'withContext' does not switch coroutines under 'runBlocking'? - multithreading

I want to find out the executing order and thread switching of kotlin coroutines. I used withContext to switch to another context and run time consuming tasks, so the main thread won't be blocked. But kotlin did not switch context as expected.
Code runs on kotlin playground: https://pl.kotl.in/V0lbCU25K
Case that doesn't work
suspend fun main() = runBlocking {
println("Hello, world!!!")
println(Thread.currentThread().name)
withContext(Dispatchers.IO) {
println("Before heavy load: ${Thread.currentThread().name}")
Thread.sleep(5000)
println("After heavy load: ${Thread.currentThread().name}")
}
println("waiting")
println(Thread.currentThread().name)
}
Outputs
Hello, world!!!
main #coroutine#1
Before heavy load: DefaultDispatcher-worker-1 #coroutine#1
After heavy load: DefaultDispatcher-worker-1 #coroutine#1
waiting
main #coroutine#1
The sleep function in the above code blocks runs at the same thread as main thread and blocks it.
Below cases match my expectation(Time consuming task does not block main thread)
Case 1
suspend fun main() = runBlocking {
println("Hello, world!!!")
println(Thread.currentThread().name)
launch {
println("Before heavy load: ${Thread.currentThread().name}")
Thread.sleep(5000)
println("After heavy load: ${Thread.currentThread().name}")
}
println("waiting")
println(Thread.currentThread().name)
}
Outputs
Hello, world!!!
main #coroutine#1
waiting
main #coroutine#1
Before heavy load: main #coroutine#2
After heavy load: main #coroutine#2
Case 2
suspend fun main() = runBlocking {
println("Hello, world!!!")
println(Thread.currentThread().name)
launch {
withContext(Dispatchers.IO) {
println("Before heavy load: ${Thread.currentThread().name}")
Thread.sleep(5000)
println("After heavy load: ${Thread.currentThread().name}")
}
}
println("waiting")
println(Thread.currentThread().name)
}
Outputs
Hello, world!!!
main #coroutine#1
waiting
main #coroutine#1
Before heavy load: DefaultDispatcher-worker-1 #coroutine#2
After heavy load: DefaultDispatcher-worker-1 #coroutine#2

I used withContext to switch to another context and run time consuming tasks, so the main thread won't be blocked. But kotlin did not switch context as expected.
Your withContext call did indeed free up the main thread. It transferred the work to another thread, but at that point your main thread was left with nothing else to do but wait for the withContext invocation to complete. runBlocking starts an event loop that can serve any number of concurrent coroutines, but since you have only one, that one coroutine had to complete in order for the runBlocking block to complete.
Here's a demonstration of what it means that the thread isn't blocked:
fun main() {
measureTimeMillis {
runBlocking {
launchManyCoroutines()
println("Top-level coroutine sleeping on thread ${currentThread().name}")
delay(2_000)
println("Top-level coroutine done")
}
}.also { println("Program done in $it milliseconds") }
}
private fun CoroutineScope.launchManyCoroutines() {
val cpuCount = getRuntime().availableProcessors()
(1 until cpuCount).forEach { coroId ->
launch { // on the main thread
val sum = withContext(Dispatchers.Default) {
println("Coroutine #$coroId computing on thread ${currentThread().name}")
computeResult()
}
println("Coroutine #$coroId done on thread ${currentThread().name}:" +
" sum = $sum")
}
}
(cpuCount + 1..100).forEach { coroId ->
launch { // on the main thread
println("Coroutine $coroId sleeping 1 s on thread ${currentThread().name}")
delay(1_000)
println("Coroutine #$coroId done on thread ${currentThread().name}")
}
}
}
private fun computeResult(): Int {
val rnd = ThreadLocalRandom.current()
return (1..1_000_000).map { rnd.nextInt() }.sum()
}
This program launches (100 + availableProcessors) concurrent coroutines, all on the main thread. Some of them use withContext(Dispatchers.Default) to perform a CPU-intensive task (summing a million random integers) on a thread pool, while others perform suspending work (delay for one second) directly on the main thread. Finally, the top-level coroutine sleeps for 2 seconds before completing.
The entire program completes in just above 2 seconds and prints something like the following:
Top-level coroutine sleeping on thread main
Coroutine #2 computing on thread DefaultDispatcher-worker-2
Coroutine #3 computing on thread DefaultDispatcher-worker-3
Coroutine #4 computing on thread DefaultDispatcher-worker-5
Coroutine #1 computing on thread DefaultDispatcher-worker-1
Coroutine #5 computing on thread DefaultDispatcher-worker-6
Coroutine #6 computing on thread DefaultDispatcher-worker-4
Coroutine #7 computing on thread DefaultDispatcher-worker-8
Coroutine 9 sleeping 1 s on thread main
Coroutine 10 sleeping 1 s on thread main
...
Coroutine 99 sleeping 1 s on thread main
Coroutine 100 sleeping 1 s on thread main
Coroutine #3 done on thread main: sum = -1248358970
Coroutine #4 done on thread main: sum = -228252033
Coroutine #6 done on thread main: sum = -147126590
Coroutine #2 done on thread main: sum = -1065374439
Coroutine #1 done on thread main: sum = -2029316381
Coroutine #7 done on thread main: sum = -865387844
Coroutine #5 done on thread main: sum = -1695642504
Coroutine #9 done on thread main
Coroutine #10 done on thread main
...
Coroutine #99 done on thread main
Coroutine #100 done on thread main
Top-level coroutine done
Program done in 2066 milliseconds
Note than everything the program does, happens while the main coroutine is sleeping on the main thread.

TL;DR
Use launch
withContext doesn't do async. It merges the contexts. To do an async job use the launch function with the specified coroutine context.
withContext does not actually run async, it merges the contexts.
According to the Kotlin core docs:
withContext:
Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result.
So the expected result of this code using withContext:
fun main() = runBlocking {
println("Before block ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
println("Long op ${Thread.currentThread().name}")
delay(1000)
}
println("After block")
}
is:
Before block main #coroutine#1
Long op DefaultDispatcher-worker-1 #coroutine#1
After block
What you want can be achieved using launch function inside your coroutine:
The result of the below code:
fun main() = runBlocking {
println("Before block ${Thread.currentThread().name}")
launch(Dispatchers.IO) {
println("Long op ${Thread.currentThread().name}")
delay(1000)
}
println("After block")
}
will be:
Before block main #coroutine#1
After block
Long op DefaultDispatcher-worker-1 #coroutine#2

Related

Why is there code that is executed when the thread is blocked using coroutines?

Why does it print 'a' after 1 second if the thread is blocked for 3 seconds?
When a thread is blocked it can't do any computation until it is unlocked. How come is it possible to execute println('a') while this thread is blocked by Thread.sleep()?
import kotlinx.coroutines.*
fun main() = runBlocking(Dispatchers.Default) {
GlobalScope.launch(Dispatchers.Default) {
delay(1000L)
println("a")
}
Thread.sleep(3000L)
}
Default Dispatcher schedules coroutines to its own thread pool, so in your example delay(1000L) and Thread.sleep(3000L) are executed in separate threads.

Running blocking CPU bound tasks on Kotlin coroutines

I have been experimenting with Kotlin and running blocking CPU tasks on kotlin coroutines. When things are blocking such as big cpu intensive computations we dont really have suspension but rather we need to launch things on different threads and let them run in parallel.
I managed to get the following code working as expected with async + Default dispatcher but wondered if it was gonna work with withContext and it did not.
fun cpuBlockingTasks() = runBlocking {
val time = measureTimeMillis {
val t1 = cpuTask(id = 1, blockTime = 500)
val t2 = cpuTask(id = 2, blockTime = 2000)
println("The answer is ${t1 + t2}")
}
println("Time taken: $time")
}
suspend fun cpuTask(id: Int, blockTime: Long): Int = withContext(Dispatchers.Default) {
println("work $id start ${getThreadName()}")
val res = doSomeCpuIntensiveTask(blockTime)
println("work $id end ${getThreadName()}")
res
}
fun doSomeCpuIntensiveTask(time: Long): Int {
Thread.sleep(time) // to mimick actual thread blocking / cpu work
return 1
}
This code completes in >2500 ms and runs on the same thread sequentially. I was expecting it to kick off the first coroutine in a thread, immediately return to the caller and kick of the second on a different thread but did not work like that. Anyone know why would that be and how it can be fixed without launching async coroutine in the caller function?
This it the output
work 1 start ForkJoinPool.commonPool-worker-5 #coroutine#1
work 1 end ForkJoinPool.commonPool-worker-5 #coroutine#1
work 2 start ForkJoinPool.commonPool-worker-5 #coroutine#1
work 2 end ForkJoinPool.commonPool-worker-5 #coroutine#1
The answer is 2
Time taken: 2523
You are not creating a new coroutine in cpuTask 1 and cpuTask 2. You are just switching context. It can be easily fixed with async:
fun cpuBlockingTasks() = runBlocking {
val time = measureTimeMillis {
val t1 = async { cpuTask(id = 1, blockTime = 500) }
val t2 = async { cpuTask(id = 2, blockTime = 2000) }
println("The answer is ${t1.await() + t2.await()}")
}
println("Time taken: $time") // Time taken: 2026
}

Difference between ExecutionContext.global and main Thread

I have following code:
object KafkaApi {
private implicit val main: ExecutionContextExecutor = ExecutionContext.global
private val workers = ExecutionContext.fromExecutor(Executors.newCachedThreadPool())
def main(args: Array[String]) {
foo.unsafeRunAsync(_ => ())
//foo.unsafeRunSync()
println("Hello")
}
def foo: IO[Unit] =
for {
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO.shift(workers)
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO.shift(main)
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO {
println(Thread.currentThread().getName)
}
} yield ()
}
and the output is:
main
Hello
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
scala-execution-context-global-14
scala-execution-context-global-14
scala-execution-context-global-14
What is the difference between main and scala-execution-context-global-14?
If these two are different, how to get the main thread back?
Running the code above, why the application never get terminated?
This additional question is too big for a comment so here goes my answer.
The thing is that in JVM all Threads are divided into "normal" and "daemon" threads. The important thing here is that
The Java Virtual Machine exits when the only threads running are all daemon threads.
So if you have any running non-daemon Thread, JVM thinks your application is still working even if it actually does nothing (maybe it is just waiting for some input). The "main" thread is obviously a "normal" thread. Threads created by standard ExecutionContext.global are daemon and thus don't stop your app from quitting when the main thread finishes. Threads created by Java's Executors.newCachedThreadPool are non-daemon and thus keep the application alive. There are several possible solutions:
Don't use other ExecutionContext except for the global i.e. don't use Executors.newCachedThreadPool at all. Depending on your case this might be or not be what you want.
Explicitly shutdown your custom ExecutorService when all its job is done. Be careful here because shutdown doesn't wait for all active tasks to be finished. So the code should become something like
private val pool = Executors.newCachedThreadPool
implicit private val workers = ExecutionContext.fromExecutor(pool)
// do whatever you want with workers
// optionally wait for all the work to be done
pool.shutdown()
Use custom pool that creates daemon threads. For example you could do something like this:
val workers = ExecutionContext.fromExecutor(Executors.newCachedThreadPool(new ThreadFactory {
private val defaultDelegate = Executors.defaultThreadFactory()
override def newThread(r: Runnable): Thread = {
val t = defaultDelegate.newThread(r)
//let the default factory do all the job and just override daemon-flag
t.setDaemon(true)
t
}
}))
IMHO the main trade-off between #2 and #3 is convenience vs correctness. In #3 you don't have to think where all tasks are finished so it is safe to call shutdown which is convenient. The price is that if for some reason you misjudged and your "main" thread quits before all other tasks are finished, you will not know that anything went wrong because daemon threads will be just silently killed. If you go with #2 and do the same mistake either your app will continue to run if you din't call shutdown in that code path, or you will see some warning in the log that the pool was shutdown while there still were some tasks in progress. So if this is just a middle step in a long sequence of processing what for some reason requires custom thread pool I'd probably go with #3; but if this parallel execution is the main behavior I'd go with more explicit #2 way.

How to tune akka thread pool to reject new tasks on full thread pool and task queue?

I want to use akka thread pool to perform some parallel tasks with limited resources and if there are too many tasks I want them to be rejected.
I have the following dispatcher configured with akka
my-dispatcher {
type = Dispatcher
executor = thread-pool-executor
thread-pool-executor {
core-pool-size-min = 1
core-pool-size-max = 2
task-queue-size = 5
}
}
And the following code
import java.time.LocalTime
import akka.actor.ActorSystem
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
object Main {
def main(args: Array[String]) {
val system = ActorSystem("my-system")
implicit val dispatcher = system.dispatchers.lookup("my-dispatcher")
for (_ <- 1 to 10) Future {
Thread.sleep(5000)
println(s"Finished at ${ LocalTime.now }")
}
Await.result(system.terminate(), Duration.Inf)
}
}
I expected that after the thread pool has running out free threads (maximum size is 2) and task-queue is also full (5 elements) - it will reject new Futures. However, that's what I see:
Finished at 17:36:57.858
Finished at 17:36:57.858
Finished at 17:36:57.858
Finished at 17:36:57.858
Finished at 17:36:57.858
Finished at 17:37:02.870
Finished at 17:37:02.870
Finished at 17:37:02.870
Finished at 17:37:02.870
Finished at 17:37:02.870
Thus, it invokes first 5 tasks, finishes them almost at the same time (17:36:57), and after a pause of 5 seconds (Thread.sleep(5000)) at 17:37:02 finishes the second portion of tasks. It means that when queue is full and thread pool is completely busy - instead of rejecting new futures the Future.apply(???) invocation just blocks. I want to see some sort of RejectionException or similar.
How to achieve this behaviour with akka?

Make goroutines stay running after returning from function

in Java I can make threads run for long periods of time and I don't need to stay within the function that started the thread.
Goroutines, Go's answer to Threads seem to stop running after I return from the function that started the routine.
How can I make these routines stay running and return from the calling function?
Thanks
Goroutines do continue running after the function that invokes them exits: Playground
package main
import (
"fmt"
"time"
)
func countToTen() chan bool {
done := make(chan bool)
go func() {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
fmt.Println(i)
}
done <- true
}()
return done
}
func main() {
done := countToTen()
fmt.Println("countToTen() exited")
// reading from the 'done' channel will block the main thread
// until there is something to read, which won't happen until
// countToTen()'s goroutine is finished
<-done
}
Note that we need to block the main thread until countToTen()'s goroutine completes. If we don't do this, the main thread will exit and all other goroutines will be stopped even if they haven't completed their task yet.
You can.
If you want to have a go-routine running in background forever, you need to have some kind of infinite loop, with some kind of graceful stopping mechanism in place, usually via channel. And invoke the go-routine via some other function, so even after this other function terminates, your go-routine will still be running.
For example:
// Go routine which will run indefinitely.
// Unless you send a signal on quit channel.
func goroutine(quit chan bool) {
for {
select {
case <-quit:
fmt.Println("quit")
return
default:
fmt.Println("Do your thing")
}
}
}
// Go routine will still be running,
// after you return from this function.
func invoker() {
q := make(chan bool)
go goroutine(q)
}
Here, you can call invoker, when you want to start the go-routine. And even after invoker returns, your go-routine will still be running in background.
Only exception to this is, when main function returns all go-routines in the application will be terminated.

Resources