kotlin Concurrency ตอนที่ 1 processes threads และ kotlin coroutines
Process
คือ program in execution และทุก processes ใช้อย่างน้อย 1 threads หรือมากกว่าในการทำงาน และ application นึงอาจมีหลาย process เช่น processId
Thread
เอาไว้เก็บ instructions สำหรับให้ processor ทำงาน โดยทุก thread สามารถแก้ไข resources ที่มีใน processได้ แต่ก็มี storage ของตัวเองโดยเฉพาะเช่นกัน เรียก thread-local storage ซึ่งจะเกี่ยวข้องกับ thread-safe อีกที เช่น
function main() // main thread
Graphic User Interface (GUI) // UI thread
thread-blocking // เมื่อ instructions ที่ทำงานใช้เวลานานเกินไป
Coroutines
มันถูกนิยามว่าเป็น lightweight threads เพราะ define the execution of a set of instructions for a processor to execute.Also, coroutines have a similar life cycle to that of threads.
1 coroutine ทำงานบน 1 thread เสมอ โดย 1 thread สามารถมีได้หลาย coroutine
coroutine สามารถย้ายไปมากับ thread อื่นๆได้ เช่น ถูกสร้างโดย thread a เริ่มทำงานบน thread b แล้วทำงานเสร็จตอน thread a
ในช่วงเวลานึง Thread นึงสามารถ execute ได้แค่ coroutine เดียวถึงแม้จะ stack coroutine ได้หลายอันก็ตาม แต่หยิบมาทำได้แค่อันเดียว
โดยปกติ coroutine ใช้เพื่อจัดการ concurrency ระหว่างหลายๆ thread ง่ายขึ้น และจัดการ Asynchronous กับ Synchronous
concurrency vs parallelism
หลักการสำคัญของสองเรื่องนี้คือ ใช้ในการ “แบ่งงาน” และแตกต่างกันที่ “จำนวน” ที่ทำ
ขอเริ่มเล่าจาก non-concurrent อย่าง Sequential ก่อนโดย code จะทำงานไปตามลำดับ
concurrency on sigle core
Parallélisme on multiple core
Sequential คือการทำงานหลายๆงานตามลำดับก่อนหลัง
Concurrent คือการสลับทำงานหลายๆอย่างในช่วงเวลาเดียวกัน
Parallelism คือการกระจายทำงานหลายๆอย่างในช่วงเวลาเดียวกัน
“Concurrency is about dealing with a lot of things at once” — Rob Pike
“Parallelism is about doing a lot of things at once ” — Rob Pike
ปัญหาของ concurrency vs parallelism
Race conditions
Atomicity violation
Deadlocks
Livelocks
Concepts เบื้องหลัง
Suspending computation
Suspending functions
Suspending lambdas
Coroutine dispatcher
Coroutine builders
Suspending computation
มองว่าเป็นกลุ่ม code ชุดนึงที่สามารถ suspend การ execution ได้โดยที่ไม่ blocking the thread ที่ทำงานอยู่ซึ่ง computation นี้จะทำงานได้ภายใต้ suspending functions เท่านั้น
Suspending functions
how it work
Suspending lambdas
คล้ายกับ suspending function เป็น anonymous local function แต่ว่าจะสั่ง suspend การ execution ได้โดย suspending function อื่น
Coroutine dispatcher
ทุกครั้งที่ coroutine start หรือ resume บน thread ใดๆและทำงานแบบไหนบน thread จะถูกกำหนดโดย CoroutineDispatcher interface
- Dispatchers.Default หากไม่มีการกำหนด Context โดย ContinuationInterceptor จะ shared pool of threads บน JVM โดยเรียกใช้ threadsได้มากสุดตามจำนวน CPU cores และขั้นต่ำ 2 thread
- Dispatchers.IO ถูกออกแบบมาสำหรับ IO tasks ที่มักจะ loading กับ blocking หนักๆ เข้าใจว่า created and shutdown ได้เองและ dispatcher ตัวนี้ shared pool of threads ร่วมกับแบบ Default
- Dispatchers.Unconfined เป็นการ dispatcher โดยไม่เจาะจง thread ที่ทำงาน โดยจะ initial coroutine ที่ current thread เสมอแต่ resume บน thread ใดก็ได้ และเนื่องจากไม่ specific thread เพราะฉะนั้น nested coroutines ใน dispatcher นี้จะทำตาม concept event-loop เพื่อหลีกเลี่ยงการ stack overflows
Coroutine builders
เป็น function suspending lambda ที่ provide สร้าง coroutine
- coroutineScope ใช้กำหนดขอบเขตในการสร้าง coroutines ของทุกๆ coroutine builder อย่างเช่น ( async, launch) จะทำงานโดยกำหนด coroutineScope ไหนและ coroutineContext ใด
- GlobalScope เป็น global CoroutineScope โดยทำงาน top-level coroutines เช่น การใช้ launch(Dispatchers.Default) { … } กับ GlobalScope.launch { … } หรือ GlobalScope.async กับ async(Dispatchers.Default) โดย scope อื่นๆนอกจาก GlobalScope ให้ใช้ Dispatchers.Unconfined
- MainScope เป็น coroutineScope สำหรับ UI components.
- runBlocking มักจะใช้ห่อ suspend code บน blocking code โดยจะทำการ block thread ปัจจุบันจนกว่า execution ของ coroutine เสร็จ เข้าใจง่ายๆคือ ใช้จองพื้นที่บน synchronous code ให้ข้างในเป็น asynchronous code โดยเมื่อ asynchronous ทั้งหมดทำงานเสร็จแล้ว จะกลับไปรัน synchronous เดิมต่อ
- async ใช้เมื่อต้องการ return result ออกมา แต่ถ้าเกิด exception จะreturn result exception ออกมาในรูปแบบ Deferred<T>
- launch จะไม่ return result ออกมา แต่จะได้ Job สำหรับ control execution
การ Shared state and concurrency
- Channels: ใช้รับและส่งข้อมูลระหว่าง coroutines
- Worker pools: pool of coroutines ที่แจกจ่ายไป processing ที่ threads ต่างๆ
- Actors: มีไว้ encapsulate state และ channel ให้ปลอดภัยมากขึ้น เมื่อ coroutine ทำงานบน thread ที่ต่างกันแต่ share state กัน
- Mutual exclusions (Mutexes)
- Thread confinement