Corotines Kotlin“ รับประกันก่อนเกิด”


10

corotines Kotlin ให้การรับประกัน "ที่เกิดขึ้นก่อนหน้า" หรือไม่?

ตัวอย่างเช่นมีการรับประกัน "ที่เกิดขึ้นก่อน" ระหว่างการเขียนถึงmutableVarและการอ่านในภายหลัง (อาจ) เธรดอื่น ๆ ในกรณีนี้:

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

แก้ไข:

บางทีตัวอย่างเพิ่มเติมอาจทำให้คำถามชัดเจนขึ้นเพราะมันมากขึ้น Kotlin-ish (ยกเว้นความไม่แน่นอน) โค้ดนี้ปลอดภัยสำหรับเธรดหรือไม่:

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

private data class Data(var data: Int)

โปรดทราบว่าเมื่อทำงานบน JVM Kotlin ใช้โมเดลหน่วยความจำเดียวกันกับ Java
Slaw

1
@ กฎหมายฉันรู้ว่า อย่างไรก็ตามมีเวทมนตร์มากมายที่เกิดขึ้นภายใต้ประทุน ดังนั้นฉันต้องการที่จะเข้าใจว่ามีสิ่งใดเกิดขึ้นก่อนที่จะรับประกันว่าฉันจะได้รับจาก coroutines หรือมันคือทั้งหมดที่เกี่ยวกับฉัน
Vasiliy

หากมีสิ่งใดตัวอย่างที่สองของคุณนำเสนอสถานการณ์ที่ง่ายยิ่งขึ้น: เพียงแค่ใช้วัตถุที่สร้างขึ้นในwithContextขณะที่ตัวอย่างที่ 1 สร้างมันขึ้นมาก่อนกลายพันธุ์ภายในwithContextแล้วอ่านหลังจากwithContextนั้น ดังนั้นตัวอย่างที่ 1 จะใช้คุณสมบัติด้านความปลอดภัยของเธรดมากขึ้น
Marko Topolnik

... และตัวอย่างทั้งสองใช้แบบ "ลำดับโปรแกรม" ที่เกิดขึ้นก่อนเหตุการณ์ที่สำคัญที่สุด ฉันกำลังพูดถึงระดับของ coroutines ที่นี่ไม่ใช่ JVM พื้นฐาน ดังนั้นโดยพื้นฐานแล้วคุณกำลังถามว่าคอร์ลินโคโตลินแตกหักอย่างรุนแรงหรือไม่โดยที่พวกเขาไม่ได้ให้คำสั่งรายการที่เกิดขึ้นก่อนหน้านี้
Marko Topolnik

1
@MarkoTopolnik แก้ไขให้ฉันถ้าฉันผิด แต่ JLS รับประกันเฉพาะ "คำสั่งของโปรแกรมที่เกิดขึ้นก่อน" สำหรับการดำเนินการในเธรดเดียวกัน ในขณะนี้ด้วย coroutines ถึงแม้ว่ารหัสจะดูเป็นลำดับ แต่ในทางปฏิบัติมีเครื่องจักรบางอย่างที่ลดการโหลดลงในเธรดที่แตกต่างกัน ฉันเข้าใจประเด็นของคุณ "นี่เป็นการรับประกันขั้นพื้นฐานที่ฉันจะไม่เสียเวลาในการตรวจสอบ" (จากความคิดเห็นอื่น) แต่ฉันถามคำถามนี้เพื่อรับคำตอบที่เข้มงวด ฉันค่อนข้างแน่ใจว่าตัวอย่างที่ฉันเขียนนั้นปลอดภัยสำหรับเธรด แต่ฉันต้องการเข้าใจว่าทำไม
Vasiliy

คำตอบ:


6

รหัสที่คุณเขียนมีสิทธิ์เข้าถึงสถานะที่ใช้ร่วมกันสามรายการ:

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

การเข้าถึงสามครั้งนั้นมีการเรียงลำดับอย่างเคร่งครัดโดยไม่มีการทำงานพร้อมกันและคุณสามารถมั่นใจได้ว่าโครงสร้างพื้นฐานของ Kotlin จะดูแลการสร้างขอบก่อนเกิดขึ้นเมื่อส่งไปยังIOเธรดพูลและกลับไปยังคอร์รูทีนการโทรของคุณ

นี่คือตัวอย่างที่เทียบเท่าที่อาจดูน่าเชื่อถือมากขึ้น:

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

เนื่องจากdelayเป็นฟังก์ชั่นที่ระงับได้และเนื่องจากเรากำลังใช้โปรแกรมเลือกจ่ายDefaultงานที่สำรองข้อมูลโดยกลุ่มเธรดบรรทัดที่ 1, 2 และ 3 อาจดำเนินการบนเธรดที่แตกต่างกัน ดังนั้นคำถามของคุณเกี่ยวกับการรับประกันที่เกิดขึ้นก่อนมีผลบังคับใช้กับตัวอย่างนี้ ในทางกลับกันในกรณีนี้มันเป็น (ฉันหวังว่า) ชัดเจนอย่างสมบูรณ์ว่าพฤติกรรมของรหัสนี้สอดคล้องกับหลักการของการดำเนินการตามลำดับ


1
ขอบคุณ จริงๆแล้วมันเป็นส่วนหนึ่งหลังจาก "มั่นใจ" ที่กระตุ้นให้ฉันถามคำถามนี้ มีลิงก์ไปยังเอกสารที่ฉันสามารถอ่านได้หรือไม่ อีกทางหนึ่งการเชื่อมโยงไปยังซอร์สโค้ดที่เกิดขึ้นก่อนที่ขอบจะเป็นประโยชน์เช่นกัน (เข้าร่วมการซิงโครไนซ์หรือวิธีอื่น ๆ )
Vasiliy

1
นี่เป็นการรับประกันขั้นพื้นฐานที่ฉันจะไม่เสียเวลาในการตรวจสอบ ภายใต้ฝากระโปรงมันเดือดลงไปexecutorService.submit()และมีบางกลไกทั่วไปของการรอคอยเมื่อเสร็จสิ้นงาน (ทำCompletableFutureหรือสิ่งที่คล้ายกัน) จากมุมมองของ Kotlin coroutines ไม่มีอะไรเกิดขึ้นพร้อมกันเลย
Marko Topolnik

1
คุณคิดว่าคำถามของคุณคล้ายกับการถาม "ระบบปฏิบัติการรับประกันสิ่งที่เกิดขึ้นก่อนหน้านี้เมื่อหยุดการทำงานเธรดแล้วกลับมาทำงานต่อบนแกนหลักอื่น" เธรดคือการดูว่าคอร์ใดของ CPU เพื่อเธรด
Marko Topolnik

1
ขอบคุณสำหรับคำอธิบายของคุณ อย่างไรก็ตามฉันถามคำถามนี้เพื่อให้เข้าใจว่าทำไมจึงใช้งานได้ ฉันเห็นประเด็นของคุณ แต่จนถึงตอนนี้มันไม่ใช่คำตอบที่จริงจังที่ฉันกำลังมองหา
Vasiliy

2
ดี ... ฉันไม่คิดว่ากระทู้นี้ได้สร้างรหัสที่เป็นลำดับ มันยืนยันแล้วแน่นอน ฉันก็อยากที่จะเห็นกลไกที่รับประกันว่าตัวอย่างนั้นจะทำงานตามที่คาดไว้โดยไม่กระทบต่อประสิทธิภาพ
G. Blake Meike

3

Coroutines ใน Kotlin จัดทำขึ้นก่อนการรับประกัน

กฎคือ: ภายใน coroutine รหัสก่อนการเรียกฟังก์ชั่นการระงับเกิดขึ้นก่อนรหัสหลังจากการโทรที่ถูกระงับ

คุณควรคิดถึง coroutines ราวกับว่ามันเป็นหัวข้อปกติ:

แม้ว่า coroutine ใน Kotlin สามารถรันบนหลายเธรดได้ แต่มันก็เหมือนกับเธรดจากสถานะที่ไม่แน่นอน ไม่มีสองการกระทำใน coroutine เดียวกันสามารถเกิดขึ้นพร้อมกัน

ที่มา: https://proandroiddev.com/what-is-concurrent-access-to-mutable-state-f386e5cb8292

กลับไปที่ตัวอย่างรหัส การถ่าย vars ในแลมด้าฟังก์ชั่นร่างกายไม่ใช่ความคิดที่ดีที่สุดโดยเฉพาะเมื่อแลมบ์ดาเป็น coroutine รหัสก่อนแลมบ์ดาจะไม่เกิดขึ้นก่อนรหัสข้างใน

ดูhttps://youtrack.jetbrains.com/issue/KT-15514


กฎคือสิ่งนี้จริง ๆ แล้ว: รหัสก่อนการเรียกฟังก์ชั่น suspend จะเกิดขึ้นก่อนที่โค้ดภายในฟังก์ชั่น suspend จะเกิดขึ้นก่อนโค้ดหลังจากการโทรที่ถูกระงับ ในทางกลับกันสามารถเป็น "คำสั่งโปรแกรมของรหัสนี้เป็นรหัสที่เกิดขึ้นก่อนการสั่งซื้อ" สังเกตว่าไม่มีสิ่งใดที่เฉพาะเจาะจงสำหรับฟังก์ชั่นที่หยุดชั่วคราวในคำสั่งนั้น
Marko Topolnik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.