KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN @kpgalligan - - PowerPoint PPT Presentation

kotlin native concurrency explained kevin galligan
SMART_READER_LITE
LIVE PREVIEW

KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN @kpgalligan - - PowerPoint PPT Presentation

KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN @kpgalligan Copenhagen Denmark Touchlab [Your Org Here] reach out if interested, obv kevin@touchlab.co @kpgalligan I still have a talk! and then multithreaded


slide-1
SLIDE 1

Copenhagen Denmark

KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN

@kpgalligan

slide-2
SLIDE 2

Touchlab

slide-3
SLIDE 3
slide-4
SLIDE 4

[Your Org Here]

reach out if interested, obv

slide-5
SLIDE 5
slide-6
SLIDE 6
slide-7
SLIDE 7

kevin@touchlab.co @kpgalligan

👌

slide-8
SLIDE 8
slide-9
SLIDE 9

🔦

slide-10
SLIDE 10

I still have a talk!

and then…

slide-11
SLIDE 11

multithreaded coroutines!

slide-12
SLIDE 12

Intro

slide-13
SLIDE 13

What is Kotlin Multiplatform?

slide-14
SLIDE 14

kot·lin mul·ti·plat·form

/ˌkätˈlin məltiˈplatfôrm,ˌkätˈlin məltīˈplatfôrm/

noun noun: kotlin multiplatform 1.optional, natively-integrated, open-source, code sharing platform, based on the popular, modern language kotlin. facilitates non-ui logic availability on many platforms.

slide-15
SLIDE 15

modern

slide-16
SLIDE 16

Saner Concurrency?

concurrency is hard

slide-17
SLIDE 17
slide-18
SLIDE 18

Kotlin Native model is controversial

lots of disagreement

slide-19
SLIDE 19

JVM model is controversial

many other platforms don’t let you do that

slide-20
SLIDE 20

JVM model is controversial

many other platforms don’t let you do that

slide-21
SLIDE 21

JVM model is controversial

many other platforms don’t let you do that

slide-22
SLIDE 22

This talk will cover

  • How the model works
  • My thoughts on concurrency and best practices
  • Libraries and future stuff (MT coroutines!)
slide-23
SLIDE 23

This talk will not cover

  • Me “selling” the model
  • Me complaining about the model
  • Debating various perspectives from the community
slide-24
SLIDE 24

Also…

no “getting around” rules

slide-25
SLIDE 25

You need to understand it

libraries won’t hide all details

slide-26
SLIDE 26

important to relax

slide-27
SLIDE 27

Kotlin Native Rules

slide-28
SLIDE 28

1) Mutable State = one thread

thread confined

slide-29
SLIDE 29

2) Immutable state = many threads

no changes mean no problems

slide-30
SLIDE 30

kevin@touchlab.co @kpgalligan

👌

slide-31
SLIDE 31

Internalize The Rules

mutable = 1/immutable = many

slide-32
SLIDE 32
  • bject BigGlobalState {

var statusString = "🚁🎹🐷" }

slide-33
SLIDE 33

Don’t manage concurrent state

no “synchronized”, “volatile”, etc

slide-34
SLIDE 34

JVM Trusts You

native does not

slide-35
SLIDE 35

Runtime Verification

slide-36
SLIDE 36

Do Things Differently

not “losing” anything

slide-37
SLIDE 37

(jvm strict mode would be nice)

slide-38
SLIDE 38

You can break rules too

just not a great idea

slide-39
SLIDE 39
  • OK. Code please?
slide-40
SLIDE 40

Worker

kn concurrency queue

slide-41
SLIDE 41

Similar to ExecutorService

Handler/MessageQueue/Looper on Android

slide-42
SLIDE 42

Worker

job queue thread

???

execute
slide-43
SLIDE 43

Worker

job queue thread

???

execute
slide-44
SLIDE 44

Worker

job queue thread

???

execute
slide-45
SLIDE 45

Worker

job queue thread

???

slide-46
SLIDE 46

Worker

job queue thread

???

slide-47
SLIDE 47

Worker

job queue thread

???

slide-48
SLIDE 48

Worker

job queue thread

???

slide-49
SLIDE 49

val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }

slide-50
SLIDE 50

val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }

slide-51
SLIDE 51

val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }

slide-52
SLIDE 52

Hello 🐷

slide-53
SLIDE 53

val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }

slide-54
SLIDE 54

public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }

slide-55
SLIDE 55

public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }

slide-56
SLIDE 56

public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }

slide-57
SLIDE 57

val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }

slide-58
SLIDE 58

public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }

slide-59
SLIDE 59

val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }.result

slide-60
SLIDE 60

public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }

slide-61
SLIDE 61

producer: () -> T1

slide-62
SLIDE 62

{"Hello 🐷"} (Unit)->String

slide-63
SLIDE 63

data class SomeData(val s:String, val i:Int) fun printStuff2() { worker.execute(TransferMode.SAFE, {SomeData("Hello 🐷🐷", 2)}) { println(it) }.result }

slide-64
SLIDE 64

data class SomeData(val s:String, val i:Int) {SomeData("Hello 🐷🐷", 2)}

(Unit)->SomeData

slide-65
SLIDE 65

data class SomeData(val s:String, val i:Int) fun printStuff2() { worker.execute(TransferMode.SAFE, {SomeData("Hello 🐷🐷", 2)}) { println(it) }.result }

slide-66
SLIDE 66

fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }

slide-67
SLIDE 67

fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }

slide-68
SLIDE 68

fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }

kotlin.IllegalStateException: Illegal transfer state

slide-69
SLIDE 69

fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }

execute wants to transfer someData between threads

slide-70
SLIDE 70

fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }

checks result (and children) for external refs

slide-71
SLIDE 71

fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }

local is still a ref till the end

slide-72
SLIDE 72

data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }

Mutable state?

slide-73
SLIDE 73

freeze()

runtime immutability designation

slide-74
SLIDE 74

public fun <T> T.freeze(): T {...}

slide-75
SLIDE 75

Freeze

  • Recursively freezes everything
  • One-way (no “unfreeze”)
  • Data may be shared between threads
Int String Float Float String Int MoreData val strData var width SomeData val moreData var count
slide-76
SLIDE 76

data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }

slide-77
SLIDE 77

data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData.freeze() }) { println(it) }.result }

slide-78
SLIDE 78

data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData.freeze() }) { println(it) }.result }

slide-79
SLIDE 79

data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData.freeze() }) { println(it) }.result }

slide-80
SLIDE 80

data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2).freeze() worker.execute(TransferMode.SAFE, { }) { println(someData) }.result }

Worker.execute must take an unbound, non-capturing function or lambda

slide-81
SLIDE 81

fun background(block:(Unit)->Unit){ worker.execute(TransferMode.SAFE, {}, block) }

slide-82
SLIDE 82

fun background(block:(Unit)->Unit){ worker.execute(TransferMode.SAFE, {}, block) }

Worker.execute must take an unbound, non-capturing function or lambda

slide-83
SLIDE 83

fun background(block:(Unit)->Unit){ worker.execute(TransferMode.SAFE, {}, block.freeze()) }

Worker.execute must take an unbound, non-capturing function or lambda

slide-84
SLIDE 84

fun background(block:()->Unit){ worker.execute(TransferMode.SAFE, {block}) { it() } }

kotlin.IllegalStateException: Illegal transfer state

slide-85
SLIDE 85

fun background(block:()->Unit){ worker.execute(TransferMode.SAFE, {block.freeze()}) { it() } }

👎

slide-86
SLIDE 86

fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) background { println(someData) } }

slide-87
SLIDE 87

fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) background { println(someData) } }

slide-88
SLIDE 88

fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) background { println(someData) } }

slide-89
SLIDE 89

class CounterModel { var count = 0 fun countClicked() { count++ } }

slide-90
SLIDE 90

class CounterModel { var count = 0 fun countClicked() { background { count++ } } }

slide-91
SLIDE 91

class CounterModel { var count = 0 fun countClicked() { background { count++ } } }

slide-92
SLIDE 92

class CounterModel { var count = 0 fun countClicked() { background { count++ } } }

slide-93
SLIDE 93

class CounterModel { var count = 0 fun countClicked() { background { count++ } } }

slide-94
SLIDE 94

class CounterModel { var count = 0 fun countClicked() { background { count++ } } } InvalidMutabilityException: mutation attempt of frozen sample.CounterModel

slide-95
SLIDE 95

Functions Have State

careful what you capture

slide-96
SLIDE 96

Global State

some special rules

slide-97
SLIDE 97

val mainOnly = SomeData("a", 1)

slide-98
SLIDE 98

val mainOnly = SomeData("a", 1)

  • bject GlobalObject {

val data = SomeData("b", 2) }

slide-99
SLIDE 99

@ThreadLocal val mainOnly = SomeData("a", 1) @ThreadLocal

  • bject GlobalObject {

val data = SomeData("b", 2) }

slide-100
SLIDE 100

@SharedImmutable val mainOnly = SomeData("a", 1) @ThreadLocal

  • bject GlobalObject {

val data = SomeData("b", 2) }

slide-101
SLIDE 101

Why Special Rules?

you can access it from anywhere

slide-102
SLIDE 102

Tips and New Stuff

slide-103
SLIDE 103

Debugging Issues

living with the new model

slide-104
SLIDE 104

InvalidMutabilityException

changing frozen state

slide-105
SLIDE 105

class CounterModel { init { ensureNeverFrozen() } var count = 0 fun countClicked() { background { count++ } } }

slide-106
SLIDE 106

Rethinking architecture

intentional mutability

slide-107
SLIDE 107

Main Thread Other Thread (maybe?)

Data Data Data

Content Model iOS View Model

slide-108
SLIDE 108

Atomics

mutating frozen state

slide-109
SLIDE 109

class CounterModel { val count = AtomicInt(0) fun countClicked() { background { count.value++ } } }

slide-110
SLIDE 110

class CounterModel { val count = AtomicInt(0) fun countClicked() { background { count.value++ } } }

slide-111
SLIDE 111

class CounterModel { val count = AtomicInt(0) fun countClicked() { background { count.value++ } } }

slide-112
SLIDE 112

val atom = AtomicReference<SomeData>(SomeData("", 0))

slide-113
SLIDE 113

class SomeModel { val atom = atomic(SomeData("abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }

slide-114
SLIDE 114

class SomeModel { val atom = atomic(SomeData("abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }

slide-115
SLIDE 115

class SomeModel { val atom = atomic(SomeData("abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }

slide-116
SLIDE 116

class SomeModel { val atom = atomic(SomeData("abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }

slide-117
SLIDE 117

Atomic-fu/Stately

slide-118
SLIDE 118

Don’t use too many atomics

AtomicRef can leak memory

slide-119
SLIDE 119

DetachedObjectGraph

transfer state

slide-120
SLIDE 120

DetachedObjectGraph

transfer state

slide-121
SLIDE 121

Worker dispatched state

mutable state thread local

slide-122
SLIDE 122

State Thread State Worker ??? State Container

Data
slide-123
SLIDE 123

State Thread State Worker ??? State Container ???

Data
slide-124
SLIDE 124

Multiplatform State

freeze in common

slide-125
SLIDE 125

freeze() is native only

slide-126
SLIDE 126

expect fun <T> T.freeze(): T expect fun <T> T.isFrozen(): Boolean expect fun <T> T.isNativeFrozen(): Boolean expect fun Any.ensureNeverFrozen()

slide-127
SLIDE 127

expect fun <T> T.freeze(): T expect fun <T> T.isFrozen(): Boolean expect fun <T> T.isNativeFrozen(): Boolean expect fun Any.ensureNeverFrozen()

slide-128
SLIDE 128

Multithreaded Coroutines

slide-129
SLIDE 129

General Thoughts

what does this mean?

slide-130
SLIDE 130

State Model Clarity

strict mode is sticking around

slide-131
SLIDE 131

Library Development

was a blocker

slide-132
SLIDE 132

KMP is more “real”

slide-133
SLIDE 133
slide-134
SLIDE 134

Disclaimers

just fyi

slide-135
SLIDE 135
slide-136
SLIDE 136

Overview

coroutine always bound to a single thread create with newSingleThreadContext

  • n native that uses Worker

Default has single background thread Main defined for Apple targets

Windows/Linux ¯\_(ツ)_/¯

slide-137
SLIDE 137

Switching Threads

use withContext (or equivalents) data passed in (or captured) is frozen data returned is frozen

slide-138
SLIDE 138

fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) background { println(someData) } }

slide-139
SLIDE 139

suspend fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) withContext(workerDispatcher) { println(someData) } }

slide-140
SLIDE 140

suspend fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) withContext(workerDispatcher) { println(someData) } }

slide-141
SLIDE 141

suspend fun printStuffForeground() { val someData = SomeData("Hello 🐷🐷", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }

slide-142
SLIDE 142

suspend fun printStuffForeground() { val someData = SomeData("Hello 🐷🐷", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }

slide-143
SLIDE 143

suspend fun printStuffForeground() { val someData = SomeData("Hello 🐷🐷", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }

slide-144
SLIDE 144

SomeData(s=Hello 🐷🐷, Dogs!, i=3) I am frozen? true

slide-145
SLIDE 145

class DbModel(private val dbId:Int){ fun showDbStuff() = mainScope.launch { val sd = withContext(workerDispatcher){ DB.find(dbId) } println(sd) } init { ensureNeverFrozen() } }

slide-146
SLIDE 146

class DbModel(private val dbId:Int){ fun showDbStuff() = mainScope.launch { val capturedId = dbId val sd = withContext(workerDispatcher){ DB.find(capturedId) } println(sd) } init { ensureNeverFrozen() } }

slide-147
SLIDE 147

class DbModel(private val dbId: Int) { fun showDbStuff() = mainScope.launch { val sd = loadDbInfo(dbId) println(sd) } private suspend fun loadDbInfo(id: Int) = withContext(workerDispatcher) { DB.find(id) } init { ensureNeverFrozen() } }

slide-148
SLIDE 148

class DbModel(private val dbId: Int) { fun showDbStuff() = mainScope.launch { val sd = loadDbInfo(dbId) println(sd) } private suspend fun loadDbInfo(id: Int) = withContext(workerDispatcher) { DB.find(id) } init { ensureNeverFrozen() } }

slide-149
SLIDE 149

class DbModel(private val dbId: Int) { fun showDbStuff() = mainScope.launch { val sd = loadDbInfo(dbId) println(sd) } private suspend fun loadDbInfo(id: Int) = withContext(workerDispatcher) { DB.find(id) } init { ensureNeverFrozen() } }

slide-150
SLIDE 150

Flow, Channel, etc

all freezable

slide-151
SLIDE 151

Main Thread Other Thread (maybe?)

Data Data Data

Content Model iOS View Model

slide-152
SLIDE 152

init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }

slide-153
SLIDE 153

init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }

slide-154
SLIDE 154

init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }

slide-155
SLIDE 155

init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }

slide-156
SLIDE 156

init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }

slide-157
SLIDE 157

init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }

slide-158
SLIDE 158

Community Libraries

slide-159
SLIDE 159

Stately?

slide-160
SLIDE 160
slide-161
SLIDE 161
slide-162
SLIDE 162

Stately v1 (Probably) deprecated

a lot of it, anyway

slide-163
SLIDE 163

@Deprecated Stately Collections

slide-164
SLIDE 164

@Discouraged Stately Collections

slide-165
SLIDE 165

January

that whole “holidays” thing

slide-166
SLIDE 166

github.com/Autodesk/coroutineworker

slide-167
SLIDE 167

github.com/Badoo/reaktive

slide-168
SLIDE 168

Next Steps

slide-169
SLIDE 169

go.touchlab.co/knthreads

slide-170
SLIDE 170

JetBrains Links

  • https://github.com/JetBrains/kotlin-native/blob/

master/IMMUTABILITY.md

  • https://github.com/JetBrains/kotlin-native/blob/

master/CONCURRENCY.md

  • https://www.youtube.com/watch?v=nw6YTfEyfO0
slide-171
SLIDE 171

go.touchlab.co/mtco

slide-172
SLIDE 172

Kotlin Koans

for native and state

slide-173
SLIDE 173

January

  • r whenever MT coroutines are stable(ish)
slide-174
SLIDE 174

Try out MT Coroutines?

some assembly required

slide-175
SLIDE 175

Build and deploy local

assuming no updates from kotlinx.coroutines

slide-176
SLIDE 176

> git clone -b native-mt \ https://github.com/Kotlin/kotlinx.coroutines.git > cd kotlinx.coroutines/ > ./gradlew build publishToMavenLocal

slide-177
SLIDE 177

Samples

from slides https://github.com/kpgalligan/MTCoroutines Droidcon app https://github.com/touchlab/DroidconKotlin/ (look for the kpg/mt_coroutines branch)

slide-178
SLIDE 178

KMP evaluation kit

slide-179
SLIDE 179

KaMP-Kit

go.touchlab.co/KaMP-Kit

slide-180
SLIDE 180

Stickers!

slide-181
SLIDE 181

#KotlinConf

THANK YOU AND REMEMBER TO VOTE

Kevin Galligan @kpgalligan

slide-182
SLIDE 182

Thanks!

@kpgalligan touchlab.co

slide-183
SLIDE 183

Thanks!

@kpgalligan touchlab.co

J

  • i

n t h e t e a m !