Skip to content

Commit

Permalink
coroutines
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenbmfj committed Mar 7, 2019
1 parent b63a154 commit dbce67b
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 1 deletion.
8 changes: 8 additions & 0 deletions coroutines/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
kotlin("jvm")
}

dependencies {
implementation(kotlin("stdlib-jdk8"))
compile("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1")
}
47 changes: 47 additions & 0 deletions coroutines/src/main/java/io/mfj/kotlinnight/coroutines/FanOut.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.mfj.kotlinnight.coroutines

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import java.util.*

// Lets look through a more real example

// We have a source of things. In this case, integers 0 - 1000

// We need to process each one, and want to use 10 workers to do it.

fun main() = runBlocking {

val workerCount = 10

val source = (0..1000).asSequence()

// Create a channel that we can receive items from.
val channel = produce {
source.forEach {
send(it)
}
}

(0 until workerCount)
.forEach { worker ->
// launch a worker to read items from the channel and process them.
launch {
for ( item in channel ) {
process(worker,item)
}
}
}

}

val rand = Random()

fun process(worker:Int,item:Int) {
Thread.sleep( rand.nextInt(10).toLong() )
println("[${worker}] ${item}")
}

// send is the suspension point. Every time send is called,
// that is the end of the continuation, and the dispatcher
// can switch to another coroutine.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.mfj.kotlinnight.coroutines

// How do we cooperate so it can switch processes?

// The compiler turns every block between suspension points into a Continuation.
// It keeps track of where in a suspension function progress is,
// so it can suspend and switch to another coroutine and then come back
// and find the continuation and restart.
// Instead of a thread context switch, it is running a switch statement.

// See:
// Deep Dive into Coroutines on JVM by RomanElizarov
// https://www.youtube.com/watch?v=YrrUCSi72E8
74 changes: 74 additions & 0 deletions coroutines/src/main/java/io/mfj/kotlinnight/coroutines/Intro.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.mfj.kotlinnight.coroutines

import kotlinx.coroutines.*

// We have 8 and 16 core computers.
// Let's use them.

// The old way - fire up a bunch of threads.
// Thread creation and switching is expensive.
// If you have 10 or 100 threads you are okay. 1000, 10000 threads is not going to perform well.
// Threaded parallelism spends a lot of time blocking.

// Coroutines lets us parallelize many processes without using more threads than we have cores.
// The coroutine dispatchers sets up a thead pool, and we can use that dispatcher for multiple thingss.

// With Threads, we rely on the OS to forcibly switch us out.
// With coroutines, we cooperate.

// suspend keyword means the function does not return immediately
suspend fun getToken(): String {
// hit some backend
return "abcdefd"
}

suspend fun post( body:String ): String {
val token = getToken() // async
val result = doPost( token, body ) // async
return result
}

fun doPost( token:String, body:String ): String {
// hit some backend
return """{"result":"success"}"""
}

// Where is the suspending actually happening?
// look for the little marks in the IDE

// we can do normal things like loops and exception handling.
suspend fun makeRequests() {
(0..3).forEach {
try {
val response = post( "request ${it}" )
println(response)
} catch ( e:Exception ) {
e.printStackTrace()
}
}
}

// You cannot call a suspending function from a non-suspended context
// Try removing "suspend" from makeRequests

// How do we call a suspending function?

// Use coroutine builders

fun main() {
// launch starts a coroutine scope in the GlobalScope coroutine dispatcher.
// This is where the forking happens.
val job:Job = GlobalScope.launch {
makeRequests()
}
println("hello")
runBlocking {
job.join()
}
println( "done" )
}

// For more in-depth, see Roman Elizarov's presentations at KotlinConf:
// https://www.youtube.com/watch?v=_hfBv0a09Jc
// https://www.youtube.com/watch?v=a3agLJQ6vt8
// Watch both before you try to write any code - some stuff showed in the first one changed before 1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.mfj.kotlinnight.coroutines

import kotlinx.coroutines.*

// a million threads would be a problem.
// we can do a million jobs in a coroutine no problem.
fun main() = runBlocking {
val jobs = List(100_000) {
launch {
delay(1000) // suspend for 1 second
print(".")
}
}
jobs.forEach { it.join() }
println("done")
}
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ include("intro")
include("library-common")
include("library-web-javalin")
include("library-web-ktor")
include("library-gui")
include("library-gui")
include("coroutines")

0 comments on commit dbce67b

Please sign in to comment.