Coroutine(コルーチン)はネットワークI/O待ちになると他のCoroutineが実行されるのか?
Coroutineが通信処理のようなI/O待ちになったとき、そのCoroutineを実行していたスレッドはブロッキングされずに、他のCoroutineが実行されるんだろうか。されない気がするけどこれ実験するの面倒くさい奴だな、どうしよう。
— 竹内裕昭 (@takke) February 20, 2019
単にタイムアウトというかめっちゃSleepしてから返却するようなURLを用意して大量のCoroutine作って叩いてみればいいんだな。AsyncTaskだとすぐに渋滞するのは何度も経験した。
— 竹内裕昭 (@takke) February 20, 2019
・・・という疑問を解消するために少し実験してみました。
AsyncTask の場合
まずはさくっと AsyncTask で実験します。
事前に適当な PHP スクリプトを置いておきます。
<?php
sleep(2);
echo date(DATE_ATOM);
?>
class MainActivity : AppCompatActivity() {
private val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
Handler().post {
startNetworkEventByAsyncTask()
}
}
private fun startNetworkEventByAsyncTask() {
repeat(20) {
val taskName = "async-task#$it"
MyTask(taskName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
}
class MyTask(private val taskName: String) : AsyncTask<Void, Void, Void>() {
private val tag = javaClass.simpleName
override fun doInBackground(vararg params: Void?): Void? {
Log.d(tag, "$taskName: start")
val response = doNetworkTask()
response.use {
Log.d(tag, "$taskName: response=" + response.code())
}
return null
}
override fun onPostExecute(result: Void?) {
Log.d(tag, "$taskName: done")
}
}
companion object {
lateinit var client: OkHttpClient
private fun doNetworkTask(): Response {
val request = Request.Builder()
.url("https://自分のサーバ/sleep.php")
.build()
return client.newCall(request).execute()
}
}
}
実行結果がこちら↓
AsyncTaskでOkHttp3で通信する場合:4スレッドずつ処理されていく pic.twitter.com/vgRWj2nyrl
— 竹内裕昭 (@takke) 2019年2月20日
見事に4スレッドずつ渋滞しながら処理されていきます。
Coroutineの場合
private fun startNetworkEventByCoroutine() {
repeat(20) {
val taskName = "coroutine#$it"
GlobalScope.launch(Dispatchers.Main) {
withContext(Dispatchers.IO) {
Log.d(tag, "$taskName: start")
val response = doNetworkTask()
response.use {
Log.d(tag, "$taskName: response=" + response.code())
}
}
Log.d(tag, "$taskName: done")
}
}
}
実行結果がこちら↓
CoroutineでOkHttp3で通信する場合:20個全部がほぼ同時にリクエストされている pic.twitter.com/r0J79nxfWE
— 竹内裕昭 (@takke) 2019年2月20日
ちょっと分かりにくいですが、ちゃんと20個全部が start に到達し、その後順に response を受けていきます。最初に同時に受け取ったレスポンスが9個しかないのはサーバ側の同時実行数の問題ですね。。
2019-02-20 14:43:09.246 8097-8153/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#0: start
2019-02-20 14:43:09.251 8097-8154/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#1: start
2019-02-20 14:43:09.252 8097-8156/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#2: start
2019-02-20 14:43:09.258 8097-8160/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#5: start
2019-02-20 14:43:09.268 8097-8155/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#6: start
2019-02-20 14:43:09.268 8097-8161/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#7: start
2019-02-20 14:43:09.270 8097-8157/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#3: start
2019-02-20 14:43:09.272 8097-8158/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#4: start
2019-02-20 14:43:09.276 8097-8164/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#9: start
2019-02-20 14:43:09.276 8097-8163/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#8: start
2019-02-20 14:43:09.277 8097-8162/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#10: start
2019-02-20 14:43:09.285 8097-8159/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#11: start
2019-02-20 14:43:09.292 8097-8168/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#13: start
2019-02-20 14:43:09.292 8097-8172/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#12: start
2019-02-20 14:43:09.297 8097-8165/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#15: start
2019-02-20 14:43:09.298 8097-8180/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#16: start
2019-02-20 14:43:09.300 8097-8189/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#19: start
2019-02-20 14:43:09.302 8097-8185/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#18: start
2019-02-20 14:43:09.305 8097-8179/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#17: start
2019-02-20 14:43:09.307 8097-8170/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#14: start
2019-02-20 14:43:11.692 8097-8180/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#16: response=200
2019-02-20 14:43:11.694 8097-8172/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#12: response=200
2019-02-20 14:43:11.694 8097-8168/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#13: response=200
2019-02-20 14:43:11.694 8097-8153/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#0: response=200
2019-02-20 14:43:11.695 8097-8161/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#7: response=200
2019-02-20 14:43:11.695 8097-8162/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#10: response=200
2019-02-20 14:43:11.695 8097-8165/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#15: response=200
2019-02-20 14:43:11.696 8097-8156/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#2: response=200
2019-02-20 14:43:11.696 8097-8154/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#1: response=200
2019-02-20 14:43:11.696 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#7: done
2019-02-20 14:43:11.696 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#10: done
2019-02-20 14:43:11.696 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#16: done
2019-02-20 14:43:11.697 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#15: done
2019-02-20 14:43:11.697 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#2: done
2019-02-20 14:43:11.697 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#0: done
2019-02-20 14:43:11.699 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#12: done
2019-02-20 14:43:11.700 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#13: done
2019-02-20 14:43:11.702 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#1: done
2019-02-20 14:43:12.211 8097-8170/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#14: response=200
2019-02-20 14:43:12.219 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#14: done
2019-02-20 14:43:13.228 8097-8157/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#3: response=200
2019-02-20 14:43:13.235 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#3: done
2019-02-20 14:43:13.240 8097-8160/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#5: response=200
2019-02-20 14:43:13.244 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#5: done
2019-02-20 14:43:14.234 8097-8158/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#4: response=200
2019-02-20 14:43:14.234 8097-8185/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#18: response=200
2019-02-20 14:43:14.246 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#4: done
2019-02-20 14:43:14.247 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#18: done
2019-02-20 14:43:14.248 8097-8155/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#6: response=200
2019-02-20 14:43:14.249 8097-8189/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#19: response=200
2019-02-20 14:43:14.252 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#6: done
2019-02-20 14:43:14.255 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#19: done
2019-02-20 14:43:14.255 8097-8179/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#17: response=200
2019-02-20 14:43:14.256 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#17: done
2019-02-20 14:43:14.414 8097-8164/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#9: response=200
2019-02-20 14:43:14.421 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#9: done
2019-02-20 14:43:15.149 8097-8159/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#11: response=200
2019-02-20 14:43:15.150 8097-8163/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#8: response=200
2019-02-20 14:43:15.154 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#11: done
2019-02-20 14:43:15.155 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#8: done
まとめ
CoroutineでネットワークI/O待ちになってもちゃんと他のCoroutineが実行される。
AsyncTaskのようなスレッド渋滞は起きない。
当たり前の結果なんだろうけど、Coroutineラクですねぇ。
検証コード一式は https://github.com/takke/CoroutineNetworkBlockingSample に置きました。
追記:単にスレッドがたくさん作られてるだけでした。
例えば100個実行してみると、64個で「渋滞」しました。
でもなんかCoroutineが実際にいくつのスレッドで実行されてるのか調べてないしちょっと足りない気がしてきた
— 竹内裕昭 (@takke) February 20, 2019
https://t.co/P3PYcRtZb2 だと、スレッド生成された気はしますー。https://t.co/yOzPORKKqz
— Tomoya Miwa (@tomoya0x00) February 20, 2019
> uses a shared pool of on-demand created threads and is designed for offloading of IO-intensive blocking operations (like file I/O and blocking socket I/O).
おお、そうなんですね!確かにめっちゃスレッド作られてるだけですねww
— 竹内裕昭 (@takke) February 20, 2019
はいww
— Tomoya Miwa (@tomoya0x00) February 20, 2019
Dispatchers.Default だと、スレッド数が固定(確か、コア数 - 1)なので、AsyncTaskと同じように渋滞するとお見ます。
確かに Dispatchers.Default だと手元の PH-1 では 8個ずつ渋滞していました。
— 竹内裕昭 (@takke) February 20, 2019
ということで、https://t.co/yiGoclEViU を使うと 64 個とか大量のスレッドが作られちゃって逆にヤバイので気をつけて使わねば。Dispatchers.Default だとスレッド数上限が限られてるので AsyncTask と同じような動きをする、と。
— 竹内裕昭 (@takke) February 20, 2019