Kotlin coroutines xảy ra - trước khi đảm bảo?


10

Các coroutines Kotlin có cung cấp bất kỳ đảm bảo "xảy ra trước" không?

Ví dụ, có đảm bảo "xảy ra trước" giữa ghi mutableVarvà đọc tiếp theo (có khả năng) luồng khác trong trường hợp này:

suspend fun doSomething() {
    var mutableVar = 0
    withContext(Dispatchers.IO) {
        mutableVar = 1
    }
    System.out.println("value: $mutableVar")
}

Biên tập:

Có thể ví dụ bổ sung sẽ làm rõ câu hỏi tốt hơn vì nó nhiều hơn Kotlin-ish (ngoại trừ khả năng biến đổi). Mã này có an toàn không:

suspend fun doSomething() {
    var data = withContext(Dispatchers.IO) {
        Data(1)
    }
    System.out.println("value: ${data.data}")
}

private data class Data(var data: Int)

Lưu ý rằng khi chạy trên JVM Kotlin sử dụng cùng một mô hình bộ nhớ như Java.
Slaw

1
@Slaw, tôi biết điều đó. Tuy nhiên, có rất nhiều phép thuật đang diễn ra dưới mui xe. Do đó, tôi muốn hiểu liệu có bất kỳ điều gì xảy ra hay không - trước khi đảm bảo rằng tôi nhận được từ các xác chết, hoặc tất cả là do tôi.
Vasiliy

Nếu bất cứ điều gì, ví dụ thứ hai của bạn trình bày một kịch bản thậm chí đơn giản hơn: nó chỉ sử dụng một đối tượng được tạo bên trong withContext, trong khi ví dụ thứ nhất tạo ra nó trước, biến đổi bên trong withContextvà sau đó đọc sau withContext. Vì vậy, ví dụ đầu tiên thực hiện nhiều tính năng an toàn chủ đề.
Marko Topolnik

... Và cả hai ví dụ chỉ thực hiện khía cạnh "thứ tự chương trình" xảy ra - trước đây, một khía cạnh tầm thường nhất. Tôi đang nói về mức độ của các coroutines ở đây, không phải là JVM cơ bản. Vì vậy, về cơ bản, bạn đang hỏi liệu các xác chết của Kotlin có bị phá vỡ nghiêm trọng đến mức họ thậm chí không cung cấp thứ tự chương trình xảy ra hay không.
Marko Topolnik

1
@MarkoTopolnik, hãy sửa lỗi cho tôi nếu tôi sai, nhưng JLS chỉ đảm bảo "thứ tự chương trình xảy ra trước khi thực hiện trên cùng một luồng. Bây giờ, với coroutines, mặc dù mã trông tuần tự, trong thực tế, có một số máy móc giảm tải cho các luồng khác nhau. Tôi hiểu quan điểm của bạn "đây là một đảm bảo cơ bản đến nỗi tôi thậm chí sẽ không lãng phí thời gian để kiểm tra nó" (từ một bình luận khác), nhưng tôi đã hỏi câu hỏi này để có được câu trả lời nghiêm ngặt. Tôi khá chắc chắn rằng các ví dụ tôi đã viết là chủ đề an toàn, nhưng tôi muốn hiểu tại sao.
Vasiliy

Câu trả lời:


6

Mã bạn đã viết có ba lần truy cập vào trạng thái chia sẻ:

var mutableVar = 0                        // access 1, init
withContext(Dispatchers.IO) {
    mutableVar = 1                        // access 2, write
}
System.out.println("value: $mutableVar")  // access 3, read

Ba truy cập được sắp xếp theo thứ tự nghiêm ngặt, không có sự tương tranh giữa chúng và bạn có thể yên tâm rằng cơ sở hạ tầng của Kotlin sẽ thiết lập một cạnh xảy ra trước khi đưa vào nhóm xử lý IOvà quay lại coroutine của bạn.

Đây là một ví dụ tương đương có lẽ có vẻ thuyết phục hơn:

launch(Dispatchers.Default) {
    var mutableVar = 0             // 1
    delay(1)
    mutableVar = 1                 // 2
    delay(1)
    println("value: $mutableVar")  // 3
}

delaylà một chức năng có thể tạm dừng và vì chúng tôi đang sử dụng bộ Defaultđiều phối được hỗ trợ bởi nhóm luồng, nên mỗi dòng 1, 2 và 3 có thể thực thi trên một luồng khác nhau. Do đó, câu hỏi của bạn về các đảm bảo xảy ra trước khi áp dụng như nhau cho ví dụ này. Mặt khác, trong trường hợp này (tôi hy vọng) hoàn toàn rõ ràng rằng hành vi của mã này phù hợp với các nguyên tắc thực hiện tuần tự.


1
Cảm ơn. Đó thực sự là một phần sau khi "yên tâm" đã thúc đẩy tôi đặt câu hỏi này. Có bất kỳ liên kết đến tài liệu mà tôi có thể đọc? Ngoài ra, các liên kết đến mã nguồn nơi cạnh này xảy ra trước khi được thiết lập cũng sẽ giúp ích rất nhiều (tham gia, đồng bộ hóa hoặc bất kỳ phương pháp nào khác).
Vasiliy

1
Đây là một đảm bảo cơ bản mà tôi thậm chí sẽ không lãng phí thời gian để kiểm tra nó. Dưới mui xe, nó sôi sùng sục executorService.submit()và có một số cơ chế điển hình của việc chờ đợi khi hoàn thành nhiệm vụ (hoàn thành một CompletableFuturehoặc một cái gì đó tương tự). Theo quan điểm của Kotlin coroutines, không có sự đồng thời nào cả ở đây.
Marko Topolnik

1
Bạn có thể nghĩ câu hỏi của mình tương tự như câu hỏi "HĐH có đảm bảo điều đó xảy ra không - trước khi tạm dừng một luồng và sau đó nối lại nó trên lõi khác?" Chủ đề là để xác định lõi CPU là gì cho chủ đề.
Marko Topolnik

1
Cảm ơn lời giải thích của bạn. Tuy nhiên, tôi đã hỏi câu hỏi này để hiểu tại sao nó hoạt động. Tôi thấy quan điểm của bạn, nhưng, cho đến nay, đó không phải là câu trả lời khắt khe mà tôi đang tìm kiếm.
Vasiliy

2
Chà ... tôi thực sự không nghĩ rằng chủ đề này đã xác định rằng mã là tuần tự. Nó đã khẳng định nó, chắc chắn. Tôi cũng vậy, sẽ thích thú khi thấy cơ chế đảm bảo rằng ví dụ này hoạt động như mong đợi, mà không ảnh hưởng đến hiệu suất.
G. Blake Meike

3

Coroutines trong Kotlin không cung cấp xảy ra trước khi đảm bảo.

Quy tắc là: bên trong một coroutine, mã trước khi gọi hàm tạm dừng xảy ra trướcsau cuộc gọi tạm dừng.

Bạn nên nghĩ về coroutines như thể chúng là những chủ đề thông thường:

Mặc dù một coroutine trong Kotlin có thể thực thi trên nhiều luồng, nó giống như một luồng từ quan điểm của trạng thái có thể thay đổi. Không có hai hành động trong cùng một coroutine có thể được đồng thời.

Nguồn: https://proandroiddev.com/what-is-conciverse-access-to-mutable-state-f386e5cb8292

Quay trở lại ví dụ mã. Chụp vars trong các cơ quan chức năng lambda không phải là ý tưởng tốt nhất, đặc biệt khi lambda là một coroutine. Mã trước lambda không xảy ra trước mã bên trong.

Xem https://youtrack.jetbrains.com/su/KT-15514


Trên thực tế, quy tắc này là: mã trước khi có lệnh gọi hàm tạm dừng xảy ra - trước khi mã bên trong hàm tạm dừng xảy ra - trước mã sau lệnh gọi tạm dừng. Đến lượt nó, điều này có thể được khái quát thành "thứ tự chương trình của mã cũng là mã xảy ra trước khi đặt hàng". Lưu ý sự vắng mặt của bất cứ điều gì cụ thể đối với các chức năng có thể treo trong tuyên bố đó.
Marko Topolnik
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.