Kotlin coroutines
White

vnkotlin viết ngày 08/10/2018

Một trong những điểm đặc biệt đáng chú ý nhất trong Kotlin mà bạn cần chú ý thì đó chính là coroutines ! Vậy coroutines thực chất là gì ?

Rất lâu trong thế giới lập trình đã tồn tại khái niệm về thread, mình cũng không nói nhiều về thread, nhưng có điểm quan trọng với thread là nó tiêu tốn resource để khởi tạo ở mức OS, cho nên một ứng dụng kiểu như web app khi handle cỡ ngàn request là cần phải cung cấp resource cho nó.

Cùng với sự nặng nề từ thread, một khái niệm mới được sinh ra để handle được nhiều request với ít resource hơn là Actor , tiêu biểu chính là Erlang được sử dụng trong Whatsapp, (bạn có thể đọc về sự thần kỳ của whatsapp tại đây. Nếu là java, thì đó là Akka , nổi bật nhất thông qua Scala, đây là một phần khá hay mình xin dành trong một bài viết khác. Chủ yếu ở đây các actor tạo ra các process và các process này liên lạc với nhau thông qua message bất đồng bộ.

Thời gian sau đó, cùng với sự ra đời của Golang đã mang tới một khái niệm gọi là goroutines rất nổi tiếng với việc chạy song song cùng lúc nhiều coroutines với chi phí rẻ hơn nhiều so với thread. Và thật may mắn cho chúng ta vì bản thân Kotlin cũng implemented khái niệm này nhưng không mặc định. Giờ chúng ta cùng nhau tìm hiểu coroutines trong Kotlin nhé

Coroutines là gì ?
Coroutines được gọi là light-weight threads, vậy chính xác thì light-weight chỗ nào ? Đó chính là viêc coroutines không map tới native-thread mà thread điểm yếu là cần resource để quản lý các kiểu, từ đó coroutines tiêu tốn ít resource hơn so với thread và được quản lý bởi app, tóm tắt như sau :

Coroutines and the threads both are multitasking. But the difference is that threads are managed by the OS and coroutines by the users.

alt text

Để thêm phần phức tạp :D bạn có thể tham khảo hình phía trên về coroutines. Ở đây điểm quan trọng chính là tính unblocking giữa thread chính với các coroutines. Unblocking là khái niệm rất hay trong các ngôn ngữ lập trình hiện đại đều có, nhiều nhất là Javascript/NodeJS nhờ đó mà tăng tốc độ lên nhiều lần. Trở lại hình trên thì điểm đặc biệt chính là coroutines có thể gọi lẫn nhau một cách trực tiếp thay vì thông qua message như mô hình actor.

Các loại coroutines:
Coroutines không phải là đặc sản của Golang hay Kotlin nha các bạn, nó có trong rất nhiều các ngôn ngữ khác và có 02 loại chính : stackess và stackful - nghe giống EJB hell nhỉ :D Việc này liên quan chủ yếu tới việc bản thân coroutines có dùng stack để callback hay không, việc stackful sẽ phải map tới một native-thread và Kotlin implemented based on stackless không dùng stack

Dài dòng và loằng ngoằng lý thuyết về coroutines xin kết lại bằng đoạn quảng cáo coroutines trên Koltinglang như sau :

One can think of a coroutine as a light-weight thread. Like threads, coroutines can run in parallel, wait for each other and communicate. The biggest difference is that coroutines are very cheap, almost free: we can create thousands of them, and pay very little in terms of performance. True threads, on the other hand, are expensive to start and keep around. A thousand threads can be a serious challenge for a modern machine.

Cài đặt và sử dụng:
Như đã lén lút nói ở trên, coroutines không được mặc định trong Kotlin, mà vì vậy các bạn cần enable coroutines trước khi sử dụng với tấm nhãn experimental rất nhà quê (các bạn nên làm quen với experimental vì nhiều thứ hay ho trong Kotlin hay dán nhãn này lắm :P) . Đừng ngại ngùng thêm đoạn sau trong build.gradle nhé anh em:

kotlin {
    experimental {
        coroutines 'enable'
    }
}
dependencies {
    ...
    compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.27.0"
}

Sau khi Gradle download các thể loại, chúng ta cuối cùng cũng được sờ đến code:

fun main(args:Array<String>){
    println("START VNKotlin Basic - Coroutines")
    launch {
        delay(1000)
        println("Hello from Corountines")
    }
    print("END VNKotlin Basic - Coroutines")
}

Kotlin định nghĩa launch và async cho phép bạn coroutines trong đó, khác nhau thì async cho phép trả về kết quả còn launch thì không
Chạy đoạn code này ra kết quả :

START VNKotlin Basic - Coroutines
END VNKotlin Basic - Coroutines

Coroutines của tui ddaau? Lừa nhau à ?! :face_with_symbols_over_mouth:
Sếp có thể lừa bạn còn code thì không, như có nói ở trên , coroutines không map tới main thread nên đoạn delay(1000) sẽ không chạy xong thì chương trình cũng đã kết thúc, gọi là chưa đến chợ đã tiêu hết tiền vậy :D Theo tinh thần Apple thì đó không phải lỗi, đó là tính năng, chúng ta âm thầm cập nhật đoạn code lại như sau :

fun main(args:Array<String>){
    println("START VNKotlin Basic - Coroutines")
    launch {
        delay(1000)
        println("Hello from Corountines")
    }
    Thread.sleep(2000)
    print("END VNKotlin Basic - Coroutines")
}

START VNKotlin Basic - Coroutines
Hello from Corountines
END VNKotlin Basic - Coroutines

Đoạn ở trên minh họa cho việc Kotlin implemented coroutines sử dụng stackless. Tiếp theo sau là đoạn code minh họa cho tính hiệu quả của coroutines:

fun increaseByThread(){
    val c = AtomicInteger()
    for (i in 1..1_000_000)
        thread(start = true) {
            c.addAndGet(i)
        }
    println("By Thread : ${c.get()}")
}

fun increaseByCoroutines(){
    val c = AtomicInteger()
    for (i in 1..1_000_000)
        launch {
            c.addAndGet(i)
        }
    println("By Coroutines : ${c.get()}")
}
fun main(args:Array<String>){
    val threadTimeMeasure = measureTimeMillis {
        increaseByThread()
    }

    val coroutineTimeMeasure = measureTimeMillis {
        increaseByCoroutines()
    }

    println("Compare Thread vs Coroutines : ${threadTimeMeasure} <> ${coroutineTimeMeasure}")
}

02 đoạn code trên giống nhau hoàn toàn chỉ khác ở trên dùng thread, ở dưới dùng coroutines, và đây là kết quả :

Compare Thread vs Coroutines : 40766 <> 1093

Thời giạn chênh lệch tính bằng miliseconds!
Lập trình viên thấy ngon hơn là khoái, nhưng lúc nhìn lại thấy kết quả 02 hàm chạy có gì đó sai sai, thread thì luôn cộng đúng là 1784293664 , còn coroutines thì hên xui ! Đó là vì một số coroutines có thể chạy chưa xong trước khi hàm main kết thúc, thiệt là, ok fix tiếp :D

fun increaseByCoroutines(){
    val deferred = (1..1_000_000).map { n ->
       async {
           n
       }
    }
    runBlocking {
        val sum = deferred.sumBy { it.await() }
        println("By Coroutines : ${sum}")
    }
}

Giờ thì 02 kết quả đã giống nhau, nhưng mà nhìn đoạn code nhiều hơn lúc đầu. Như đã nói ở trên async là hàm cho phép chạy coroutines trả về kết quả, cụ thể ở đây sẽ trả về giá trị n từ 1 -> 1000000, sau đó giá trị được coroutines xử lý sẽ trả về qua hàm await(), hàm này ở đâu ra? Thì ra async trả về một instance của Deffered , await() là hàm từ instance này! Tới đây, tiếp tục gọi hàm sumBy của instance Deffered để tính tổng lại các giá trị coroutines trả về là xong, nhưng bạn sẽ gặp lỗi compile :

Suspend functions are only allowed to be called from a coroutine or another suspend function

Lỗi trên là do coroutines cần phải được suspend lại chờ các coroutines khác kết thúc, mà việc suspend này chỉ được thực hiện trong một coroutines mà thôi, cho nên đoạn tính tổng cần phải được để trong một coroutines nữa, ở đây là runBlocking {...} (tương tự launch{...}). Ngoài ra bạn cũng có thể định nghĩa đoạn code bên trong async {...} thành một hàm riêng biệt, nhưng để sử dụng được trong corountines bạn cần thêm từ khóa suspend phía trước nếu không muốn gặp lỗi như trên

suspend fun workload(n: Int): Int {
    delay(1000)
    return n
}

fun increaseByCoroutines(){
    val deferred = (1..1_000_000).map { n ->
        async {
            workload(n)
        }
    }
    launch {
        val sum = deferred.sumBy { it.await() }
        println("By Coroutines : ${sum}")
    }
}

Vậy là xong ! Code dài hơn nhưng chạy nhanh hơn nhỉ :D
Chắc nhiều bạn sẽ hỏi sao coroutines có lợi vậy mà không xài coroutines hết đi, bỏ thread đi blah blah ... vậy thì tốc độ ứng dụng sẽ nhanh hơn nhiều. Đúng là coroutines hay thật, nhưng thread đã dùng rất lâu và rất nhiều trong các thư viện cũng như framework, việc thay đổi hoàn toàn không thể thực hiện ngày một ngày hai, đó cũng là vấn đề gặp phải với Actor của Scala. Kết lại thì Kotlin cung cấp cho bạn những công cụ tốt nhất có thể, vấn đề là các bạn sẽ vận dụng nó như thế nào thôi. Chúc vui

Source code : https://github.com/vnkotlin/basic/tree/master/coroutines

from : https://vnkotlin.com/t/coroutines-trong-kotlin/210

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

vnkotlin

2 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
2 bài viết.
0 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!