コトリンハむドのコルヌチン


Simon Wirtzは圌のブログで、Kotlinに関する倚くの興味深い投皿を投皿しおいたす。
そのうちの䞀぀の翻蚳をお芋せしたす。

玹介ず動機


数日前にツむッタヌで述べたように、私はコヌリンのコルヌチンを詳しく調べる぀もりでした。 しかし、残念ながら、予想よりも時間がかかりたした。 ほずんどの堎合、これは、特にルヌチンの抂念に粟通しおいない堎合、コルヌチンが非垞に膚倧なトピックであるずいう事実によるものです。 いずれにせよ、私はあなたず私の意芋を共有したいず思いたす、そしお、私はあなたに培底的なレビュヌをしたいず思いたす。


JetBrains ブログで述べられおいるように、コルヌチンは間違いなく「倧きな機胜」の1぀です。


負荷がかかるずブロックするのが悪いこず、 GOがほがすべおの堎所でサンプルずしお䜿甚されおいるこず、䞖界がたすたす非同期になり、通知凊理に基づいおいるこずがわかっおいたす。 倚くの蚀語2012幎のC以降 は、 async/awaitなどの構成芁玠を䜿甚しお、すぐに非同期プログラミングをサポヌトしたす。 Kotlinでは、このような構造はラむブラリで宣蚀でき、非同期はキヌワヌドではなく、単玔な関数になるず想定しおいたした。 この蚭蚈により、 future/promises 、 callback-passingなど、さたざたな非同期APIを実装できたす。 遅延ゞェネレヌタヌ yield およびその他の機胜を実装するために必芁なものもすべおありたす。

぀たり、マルチスレッドプログラミングを簡単に実装するためのコルヌチンが導入されおいたす。 確かにあなたの倚くは、 Java 、そのThreadクラス、およびマルチスレッドプログラミングのクラスを扱っおいたす。 私自身も圌らず倚くの仕事をしおきおおり、圌らの決定の成熟床を確信しおいたす。


JavaマルチスレッドずKotlinコルヌチン


それでもJavaのスレッドずマルチスレッドで問題が発生する堎合は、 Java同時実行性の緎習垳をお勧めしたす 。


もちろん、゚ンゞニアリングの芳点から、 Javaからの実装は適切に蚭蚈されおいたすが、日垞の䜜業で䜿甚するこずは難しく、非垞に冗長です。 さらに、ノンブロッキングプログラミング甚のJava実装は倚くありたせん。 倚くの堎合、スレッドを開始するずきに、ブロックコヌドロック、期埅などにすぐにアクセスするこずを完党に忘れるずいう事実に気付くこずができたす。代替の非ブロッキングアプロヌチは 、日垞の䜜業では䜿いにくく、間違いを犯しやすいです。


䞀方、コルヌチンは単玔なシヌケンシャルコヌドのように芋え、ラむブラリ内のすべおの耇雑さを隠したす。 同時に、ロックなしで非同期コヌドを実行する機胜を提䟛したす。これにより、さたざたなアプリケヌションに倧きなチャンスが生たれたす。 スレッドをブロックする代わりに、蚈算が䞭断されたす。 JetBrainsは、コルヌチンをJavaで知っおいるスレッドではなく、「軜量スレッド」ずしお説明しおいたす 。 コルヌチンは非垞に安䟡に䜜成でき、フロヌず比范したオヌバヌヘッドは比范できたせん。 埌で芋るように、コルヌチンはラむブラリの制埡䞋でスレッドで実行されたす。 別の重芁な違いは制限です。 スレッドの数は、実際にはネむティブスレッドに察応しおいるため、制限されおいたす。 䞀方、コルヌチンの䜜成は事実䞊無料であり、数千個のコルヌチンも簡単に起動できたす。


マルチスレッドスタむル


異なる蚀語では、 callback JavaScript、 future/promise Java、JavaScript、 async/awaitアプロヌチCなどに基づいお、マルチスレッドプログラミングぞのさたざたなアプロヌチを芋぀けるこずができたす。 それらはすべお、プログラミングスタむルを課さないずいう事実により、コルヌチンを䜿甚しお実装できたす。 それどころか、どのスタむルも既に実装されおいるか、それらの助けを借りお実装するこずができたす。


利点の1぀ずしお、 callbackベヌスのアプロヌチず比范しお、コルヌチンを䜿甚するず、非同期コヌドを実装できたす。これは、シヌケンシャルコヌドのように芋えたす。 たた、コルヌチンは耇数のスレッドで実行できたすが、コヌドの䞀貫性は維持されるため、理解しやすくなりたす。


コルチンのコンセプト


これは、「コルヌチン」の抂念が新しいずいうこずではありたせん。 りィキペディアの蚘事によるず、名前自䜓は1958幎にすでに知られおいたした。 C 、 Go 、 Python 、 Rubyなど、倚くの最新のプログラミング蚀語がネむティブサポヌトを提䟛したす。 Kotlinを含むコルチンの実装は、コンピュヌタヌプログラムの制埡状態の抜象的な衚珟である、いわゆる「 継続 」に基づいおいるこずがよくありたす。 それらがどのように機胜するかコルヌチンの実装に戻りたす。


スタヌト。 基本


kotlinlang.orgには 、コルヌチンを䜿甚するようにプロゞェクトをセットアップする方法を説明する包括的な資料がありたす。 前のリンクの資料を詳しく芋るか、 GitHubのリポゞトリからコヌドをベヌスずしおください。


コルチン成分


すでに述べたように、コルヌチンラむブラリは、理解しやすい高レベルのAPIを提䟛し、すぐに開始できるようにしたす。 孊習する唯䞀の修食子は䞭断です。 メ゜ッドの远加修食子ずしお䜿甚しお、それらを割り蟌み可胜ずしおマヌクしたす。


少し埌、 APIからのいく぀かの簡単な䟋を芋おみたしょうが、今のずころは、たずは、 suspend機胜ずは䜕かを詳しく芋おみたしょう。


割り蟌み可胜な機胜


コルヌチンは、機胜が䞭断される可胜性があるこずを瀺すために䜿甚されるsuspendキヌワヌドに基づいおいたす。 ぀たり、このような関数の呌び出しはい぀でも䞭断できたす。 そのような関数はcorutinからのみ呌び出すこずができ、corutinには少なくずも1぀の実行関数が必芁です。


 suspend fun myMethod(p: String): Boolean { //... } 

䞊蚘の䟋からわかるように、割り蟌み関数は远加の修食子を持぀通垞の関数のように芋えたす。 そのような関数はコルヌチンからしか呌び出せないこずに泚意しおください。そうしないず、コンパむル゚ラヌが発生したす。


コルヌチンは、通垞の関数のシヌケンス、たたは実行埌に䜿甚可胜なオプションの結果を持぀割り蟌み関数のいずれかです。


䟋に移りたしょう。


䜕ずか䜕ずか䜕ずかしお、特定の䟋に移りたしょう。 基本から始めたしょう


 fun main(args: Array<String>) = runBlocking { //(1) val job = launch(CommonPool) { //(2) val result = suspendingFunction() //(3) println("$result") } println("The result: ") job.join() //(4) } >> prints "The result: 5" 

この䟋では、2぀の関数1 runBlockingず2 launchがコルヌチンビルダヌの䜿甚䟋です。 倚数の異なるビルドがあり、それぞれがさたざたな目的のコルヌチンを䜜成したす launch 䜜成しお忘れる、 async  promise返す、 runBlocking フロヌをブロックするなど。


2 launch䜜成された内郚コルヌチンがすべおの䜜業を行いたす。 䞭断された関数3の呌び出しはい぀でも䞭断でき、結果は蚈算埌に衚瀺されたす。 メむンスレッドでは、corutinを開始した埌、コルヌチンが完了する前にString倀が衚瀺されたす。 launchから起動されたCorutinは、すぐにJob返しJob 。これは、実行をキャンセルしたり、4 join()を䜿甚しお蚈算を埅機したりするために䜿甚できたす。 たた、 join()呌び出しは䞭断される可胜性があるため、 runBlockingがよく䜿甚される別のコルヌチンにラップする必芁がありたす。 このビルダヌ1は、割り蟌みのスタむルで蚘述されたコヌドが通垞のブロック圢匏で呌び出される機䌚を提䟛するために特別に䜜成されたした APIからの泚意。 join 4を削陀joinず、コルヌチンが結果の倀を衚瀺する前にプログラムが終了したす。


りサギの穎をもっず深く芋たしょう


より珟実に近い䟋を考えおみたしょう。 アプリケヌションでメヌルを送信する必芁があるず想像しおください。 受信者の芁求ずメッセヌゞテキストの生成にはかなりの時間がかかりたす。 䞡方のプロセスは独立しおいるため、それらを䞊行しお実行できたす。


 suspend fun sendEmail(r: String, msg: String): Boolean { //(6) delay(2000) println("Sent '$msg' to $r") return true } suspend fun getReceiverAddressFromDatabase(): String { //(4) delay(1000) return "coroutine@kotlin.org" } suspend fun sendEmailSuspending(): Boolean { val msg = async(CommonPool) { //(3) delay(500) "The message content" } val recipient = async(CommonPool) { //(5) getReceiverAddressFromDatabase() } println("Waiting for email data") val sendStatus = async(CommonPool) { sendEmail(recipient.await(), msg.await()) //(7) } return sendStatus.await() //(8) } fun main(args: Array<String>) = runBlocking(CommonPool) { //(1) val job = launch(CommonPool) { sendEmailSuspending() //(2) println("Email sent successfully.") } job.join() //(9) println("Finished") } 

䜜成者のコヌドはわずかに単玔化できたす

sendEmailSuspendingおよびmainのコヌド
 suspend fun sendEmailSuspending(): Boolean { val msg = async(CommonPool) { delay(500) "The message content" } val recipient = async(CommonPool) { getReceiverAddressFromDatabase() } println("Waiting for email data") return sendEmail(recipient.await(), msg.await()) } fun main(args: Array<String>) = runBlocking(CommonPool) { sendEmailSuspending() println("Email sent successfully.") println("Finished") } 

最初に、前の䟋で芋たように、1 runBlocking内のlaunchビルダヌを䜿甚するため、9コルヌチンが完了するのを埅぀こずができたす。 2では、割り蟌み関数sendEmailSuspendingを呌び出したす。 このメ゜ッド内では、2぀の䞊列タスクを䜿甚したす。3メッセヌゞテキストを取埗し、4 getReceiverAddressFromDatabaseを呌び出しおアドレスを取埗したす。 䞡方のタスクは、 asyncを䜿甚しお䞊列コルヌチンで実行されたす。 たた、遅延呌び出しがブロックされおいないこずに泚意する䟡倀がありたす;コルヌチンの実行を䞭断するために䜿甚されたす。


非同期ビルダヌ


このビルダヌは、コンセプトが本圓にシンプルです。 他の蚀語では、圌はpromiseを返したす。これは厳密に蚀えば、 Kotlinでは Deferred型ずしお衚されたす。 promise 、 future 、 deferred 、 delayなどのこれらの゚ンティティはすべお通垞亀換可胜であり、同じものの説明です。 蚈算の結果を返すこずを「玄束」し、い぀でも埅機できる非同期オブゞェクト。


7で、 KotlinからのDeferredオブゞェクトの「埅機」郚分を既に芋おきたした。ここでは、䞡方のメ゜ッドを埅機した結果で割り蟌み関数が呌び出されたした。 await()メ゜ッドは、 Deferredオブゞェクトのむンスタンスで呌び出され、その呌び出しは結果が利甚可胜になるたで䞭断されたす。 sendEmail呌び出しも非同期ビルダヌでラップされるため、実行を埅機できたす。


芋逃したものCoroutineContext


䞊蚘の䟋の重芁な郚分は、 CoroutineContextクラスのむンスタンスであるビルダヌ関数に枡される最初のパラメヌタヌです。 このコンテキストは、コルヌチンに枡すものであり、珟圚のJobぞのアクセスを提䟛するものです。


珟圚のコンテキストを䜿甚しお内郚コルヌチンを開始できたす。その結果、子Jobは倖郚コルヌチンの子孫になりたす。 これにより、芪Jobぞの1回の呌び出しでコルヌチン階局党䜓を取り消すこずができたす。
CoroutineContextは、 CoroutineContextさたざたなタむプのElementが含たれおいたす。


䞊蚘のすべおの䟋では、 CommonPool䜿甚さdispatcher 。これはたさにdispatcherです。 圌は、フレヌムワヌクの制埡䞋でスレッドプヌルでコルヌチンが実行されるこずを保蚌する責任がありたす。 別の方法ずしお、限定されたストリヌム、たたは特別に䜜成されたストリヌムを䜿甚するか、独自のプヌルを䜿甚できたす。 コンテキストは、+挔算子を䜿甚しお簡単に結合できたす。


 launch(CommonPool + CoroutineName("mycoroutine")){...} 

䞀般的な可倉状態


あなたはおそらくそれに぀いおすでに考えおいたしたもちろん、コルヌチンはよく芋えたすが、どのように同期し、どのように異なるコルヌチン間でデヌタを亀換したすか


さお、これはたさに私が最近尋ねた質問であり、これはスレッドプヌルを䜿甚するほずんどのコルヌチンにずっお劥圓な質問です。 同期には、スレッドセヌフなデヌタ構造、1぀のスレッドでの実行制限、ロックの䜿甚など、さたざたな手法を䜿甚できたす詳现に぀いおは、 MutexをMutex 。
䞀般的なパタヌンに加えお、 Kotlinコルヌチンは「通信による亀換」スタむルの䜿甚を掚奚したす QAを参照。


特に、俳優はコミュニケヌションに適しおいたす。 それからメッセヌゞを送受信できるコルヌチンで䜿甚できたす。 䟋を芋おみたしょう


俳優


 sealed class CounterMsg { object IncCounter : CounterMsg() // one-way message to increment counter class GetCounter(val response: SendChannel<Int>) : CounterMsg() // a request with channel for reply. } fun counterActor() = actor<CounterMsg>(CommonPool) { //(1) var counter = 0 //(9) actor state, not shared for (msg in channel) { // handle incoming messages when (msg) { is CounterMsg.IncCounter -> counter++ //(4) is CounterMsg.GetCounter -> msg.response.send(counter) //(3) } } } suspend fun getCurrentCount(counter: ActorJob<CounterMsg>): Int { //(8) val response = Channel<Int>() //(2) counter.send(CounterMsg.GetCounter(response)) val receive = response.receive() println("Counter = $receive") return receive } fun main(args: Array<String>) = runBlocking<Unit> { val counter = counterActor() launch(CommonPool) { //(5) while(getCurrentCount(counter) < 100){ delay(100) println("sending IncCounter message") counter.send(CounterMsg.IncCounter) //(7) } } launch(CommonPool) { //(6) while ( getCurrentCount(counter) < 100) { delay(200) } }.join() counter.close() // shutdown the actor } 

䞊蚘の䟋では、 Actorを䜿甚しActor 。これは、それ自䜓がコルヌチンであり、任意のコンテキストで䜿甚できたす。 アクタヌには、 counter含たれおいるアプリケヌションの珟圚の状態が含たれおいたす。 ここでたた別の興味深い機胜2 Channel䌚いたす


チャンネル


チャネルは、倀のストリヌムを亀換する機胜を提䟛したす。これは、ロックなしでJavaでBlockingQueue プロデュヌサヌ/コンシュヌマヌパタヌンを実装を䜿甚する方法に非垞に䌌おいたす。 さらに、 sendずreceiveは割り蟌み可胜な機胜であり、 FIFO戊略を実装するチャネルを介しおメッセヌゞを送受信するために䜿甚されたす。


デフォルトでは、アクタヌにはそのようなチャネルが含たれおおり、他のコルヌチンで䜿甚しおメッセヌゞを送信できたす。 䞊蚘の䟋では、アクタヌは自身のチャンネルからのメッセヌゞを繰り返し凊理しここでは割り蟌み呌び出しfor動䜜したす、タむプに応じお凊理したす。 GetCounterからSendChannel独立した倀を送信する圢匏で。


メ゜ッド5 mainの最初のコルヌチンは䟿宜䞊のものであり、カりンタヌ倀が100未満になるたでメッセヌゞ7 IncCounterをアクタヌに送信したすIncCounter番目6はカりンタヌ倀が100未満になるたで埅機したす。 8 getCurrentCounter 。これにGetCounterメッセヌゞがGetCounterれ、応答でreceiveを埅機しおいる間に䞭断さGetCounterたす。


ご芧のずおり、州党䜓が特定のアクタヌに隔離されおいたす。 これにより、䞀般的な可倉状態の問題が解決されたす。


その他の機胜ず䟋


コルヌチンをより深く掘り䞋げお䜜業したい堎合は、 Kotlinのドキュメントをより詳现に読んで、特に玠晎らしいガむドを芋るこずをお勧めしたす 。


仕組み-Corutinの実装


投皿を過負荷にしないために、詳现に深く入りすぎないようにしたす。 さらに、今埌数週間のうちに、バむトコヌド生成の䟋ずずもに、より詳现な実装情報を含む続線を曞く予定です。 そのため、ここでは「指で」簡単な説明に限定したす。


コルヌチンは、 JVMの機胜にもオペレヌティングシステムの機胜にも基づいおいたせん。 代わりに、コルヌチンおよび割り蟌み可胜関数は、コンパむラヌによっお、状態を維持しながら割り蟌みをむンタヌセプトし、割り蟌み可胜関数に送信できる状態マシンに倉換されたす。 これはすべお、 Continuationおかげで可胜です。Continuationは、コンパむラヌによっお、远加の暗黙パラメヌタヌの圢匏で、䞭断された関数の各呌び出しに远加されたす。 これは、いわゆる継続枡しスタむルです。 より詳现な説明はここにありたす 。


ロヌマン・゚リザロフからのヒント


少し前、私はJetBrainsの Roman Elizarovず話をするこずができたした。Kotlinのコルヌチンがさたざたな方法で登堎したおかげです。 この情報をあなたず共有させおください


Q最初に出おくる質問は、い぀コルヌチンを䜿甚する必芁があり、スレッドを䜿甚する必芁がある状況はありたすか


Aコルヌチンは、ほずんどの堎合に䜕かを期埅する非同期タスクに必芁です。 集䞭的なCPUタスクのスレッド。


Q「軜量スレッド」ずいうフレヌズは、特にコルヌチンがスレッドベヌスでありスレッドプヌルで実行されるこずを考えるず、少し誀解を招くように聞こえるず述べたした。 コルヌチンは、実行、䞭断、停止される「タスク」に䌌おいるように思えたす。


A「軜量ストリヌム」ずいうフレヌズはかなり衚面的なもので、コルヌチンは倚くの点でナヌザヌの芳点から芋るずストリヌムのように動䜜したす


Q同期に぀いお知りたいのですが。 コルヌチンが倚くの点でフロヌに䌌おいる堎合、異なるコルヌチン間で䞀般的な状態の同期を実珟する必芁がありたす。


A同期にはよく知られおいるパタヌンを䜿甚できたすが、コルチンを䜿甚する堎合は、䞀般的な状態をたったく持たないこずをお勧めしたす。 代わりに、コルヌチンは「コミュニケヌションを通じお亀換スタむルを奚励したす」。


結論


コルヌチンは、 Kotlinに登堎した非垞に匷力な機胜です。 コルヌチンに出䌚うたで、 Javaからのマルチスレッド化で十分であるように思えたした。


Javaずは察照的に、 Kotlinはたったく異なるスタむルの競合プログラミングを提䟛したす。これは本質的にノンブロッキングであり、膚倧な数のネむティブスレッドを匷制的に起動するこずはありたせん。 Javaでは、これが倧きなオヌバヌヘッドであるず考えずに別の远加のスレッドたたは新しいプヌルを䜜成するこずは非垞に普通であり、これにより将来アプリケヌションが遅くなる可胜性がありたす。 代替ずしおのコルヌチンは、いわゆる「軜量スレッド」であり、それにより、ネむティブスレッドず1察1で盞関せず、 deadlocks 、 starvationなどの問題を起こしにくいこずを匷調しおいたす。 これたで芋おきたように、コルヌチンを䜿甚するず、フロヌのブロックや同期に぀いお心配する必芁はありたせん。特に「通信による通信」を順守する堎合は、フロヌはより簡単に芋えたす。


たた、コルヌチンを䜿甚するず、競合するコヌドを䜜成するためのさたざたなアプロヌチを䜿甚できたす。各アプロヌチは、ラむブラリ kotlinx.coroutine に既に実装されおいるか、その助けを借りお簡単に実装できたす。


Java開発者は、タスクをスレッドプヌルに送信し、 ExecutorServiceを䜿甚しおfuture結果を埅぀こずに慣れおいる可胜性が高いですExecutorServiceは、 ExecutorService 、 async/awaitを䜿甚しお簡単に実装できたす。 はい、これは完党に同等の代替品ではありたせんが、それでも倧きな改善です。


Javaでの䞊行プログラミングぞのアプロヌチを再定矩したす。これらすべおのチェック䟋倖、ハヌドブロッキング戊略、倧量の定型コヌド。 コルヌチンの堎合、関数をsuspend呌び出しを䜿甚しおコヌドを連続しお蚘述し、他のコルヌチンず通信し、結果を埅機し、コルヌチンをキャンセルするなど、非垞に正垞です。


芋蟌み


それにもかかわらず、コルヌチンは本圓に玠晎らしいず確信しおいたす。 もちろん、負荷が高いマルチスレッドアプリケヌションにずっお本圓に成熟しおいるかどうかは時間が経おばわかりたす。 倚分倚くのプログラマヌでさえ、プログラミングぞのアプロヌチを考え、再考するでしょう。 次に䜕が起こるかを芋るのは興味深いです。 , , , JetBrains , , .


いいね . , - . .


Simon



Source: https://habr.com/ru/post/J336228/


All Articles