1.コンセプト
コルーチンはスレッド上で実行する必要があるため、スケジューラを指定する必要があります。これは抽象クラスです。Dispatcher は、スレッドの切り替えをカプセル化するのに役立つ標準ライブラリのヘルパー クラスです。コルーチンがどのタイプのスレッドで実行されるかをスケジュールできます。コルーチンの作成時に、コンテキストでスケジューラが指定または継承されていない場合は、デフォルトのスケジューラが追加されます (スケジューラは ContinuationInterceptor 継続本体インターセプタを通じて実装されます)。
スレッドは単純に指定されるわけではないため、スケジューリングはスレッドではなくディスパッチャーを通じて行われます。
2. モード
- 子コルーチンは親コルーチンのコンテキストを継承するため、親コルーチンでスケジューラ モードを指定すると、子コルーチンはデフォルトでこのモードを使用します。
- IO モードと DEFAULT モードは同じスレッド プールを共有し、最適化のためにスレッドを再利用します (DEFAULT は、同じスレッドに留まる可能性が高い IO に切り替わります)。この 2 つはスレッド数に独立した制限があり、互いに枯渇することはありません。最大限まで組み合わせて使用すると、同時にアクティブになるデフォルトのスレッド数は 64 + CPU の数になります。
ディスパッチャ.メイン | Android の UI スレッドであるメイン スレッドで実行され、UI インタラクションのいくつかの軽量タスクを処理するために使用されます。 | サスペンド関数を呼び出す UI関数を呼び出す ライブデータの更新 |
Dispatcher.Main.immediate | コルーチンのスケジューリングにはコストがかかります。すでにメインスレッドにいるときに、メインスレッドにスケジュールされたサブコルーチンを開始すると、一時停止が発生し、回復を待つことになります。これは不必要なオーバーヘッドであり、長いキューでもデータが発生します。遅延表示 (たとえば、ViewModelScope は Android のデフォルトのメイン スレッドにあるため、コンテキスト内のスケジューラーはこれを使用します) 即時として指定されている場合は、必要な場合にのみスケジュールされ、それ以外の場合は直接実行されます。 | Context でラップされた関数は、Dispatcher.Main で実行するときに使用されます。 |
Dispatcher.IO | スレッド プールで実行され、IO ブロック タスク用に最適化されています。スレッドの最大数は 64 です。これを超えず、アイドル状態のスレッドがない限り、いつでも新しいスレッドを開いて新しいタスクを実行できます。 |
データベース ファイルの読み書き ネットワーク処理 |
ディスパッチャー.デフォルト | スレッド プール上で実行され、CPU 集中型のコンピューティング タスク向けに最適化されています。スレッドの最大数は CPU コアの数 (ただし 2 つ以上) であり、すべてがビジー状態の場合、新しいタスクを実行できません。 |
配列のソート Json の解析 不一致の判断への対応 ビットマップの計算 |
Dispatcher.Unconfined | スレッドを変更したり、スレッドの実行を開始したり、スレッドの実行を再開したりしないでください。スケジューリングはコストが最も低く、パフォーマンスも最高ですが、メインスレッドでブロック操作を呼び出すリスクがあります。 | コルーチンがどのスレッドで中断されているかを気にする必要がない場合に使用されます。 |
3. スレッド数を制限するlimitedParallelism()
バージョン1.6で導入されました。
- デフォルト モードの場合: 非常に負荷の高いタスクがあり、同じスケジューラを使用する他のコルーチンがスレッドの実行権限を取得できない可能性がある場合、今回はコルーチンが使用するスレッドの数を制限するために使用できます。
- IO モードの場合: 非常に高価なタスクがある場合、ブロックされるスレッドが多すぎるため、他のタスクが一時停止して待機し、実行を高速化するためにデフォルトの制限である 64 スレッドを突破することがあります (重要ではありません)。
- パラメーター転送では、データを同時に変更する複数のスレッドの同期の問題を解決するために、スレッドを 1 に制限します。ただし、ブロックされている場合、他の操作は待機する必要があります。
public open fun LimitedParallelism(並列処理: Int): CoroutineDispatcher |
suspend fun main(): Unit = coroutineScope {
//使用默认IO模式
launch {
printTime(Dispatchers.IO) //打印:Dispatchers.IO 花费了: 2038/
}
//使用limitedParallelism增加线程
launch {
val dispatcher = Dispatchers.IO.limitedParallelism(100)
printTime(dispatcher) //打印:LimitedDispatcher@1cc12797 花费了: 1037
}
}
suspend fun printTime(dispatcher: CoroutineDispatcher) {
val time = measureTimeMillis {
coroutineScope {
repeat(100) {
launch(dispatcher) {
Thread.sleep(1000)
}
}
}
}
println("$dispatcher 花费了: $time")
}
4. マルチスレッドの同時実行の問題
10 個のコルーチンを作成し、各コルーチンは i++ を 1000 回実行し、期待される結果は i=10000 になります。
4.1 共有変数の使用を避ける
fun main() = runBlocking {
val deferreds = mutableListOf<Deferred<Int>>()
repeat(10) {
val deferred = async(Dispatchers.Default) {
var i = 0
repeat(1000) { i++ }
return@async i
}
deferreds.add(deferred)
}
var result = 0
deferreds.forEach {
result += it.await()
}
println("i = $result")
}
打印:i = 10000,耗时:77
4.2 Java メソッドの使用 (非推奨)
同期、ロック、アトミックが使用できます。スレッド モデルでのブロッキング メソッドであるため、サスペンション関数の呼び出しはサポートされておらず、コルーチンのサスペンション機能に影響します。
4.2.1 同期ロックの使用
fun main() = runBlocking {
val start = System.currentTimeMillis()
var i = 0
val jobs = mutableListOf<Job>()
@Synchronized
fun add() { i++ }
repeat(10) {
val job = launch(Dispatchers.Default) {
repeat(1000) { add() }
}
jobs.add(job)
}
jobs.joinAll()
println("i = $i,耗时:${System.currentTimeMillis() - start}")
}
//打印:i = 10000,耗时:71
4.2.2 同期されたコードブロックの使用
fun main() = runBlocking {
val start = System.currentTimeMillis()
val lock = Any()
var i = 0
val jobs = mutableListOf<Job>()
repeat(10) {
val job = launch(Dispatchers.Default) {
repeat(1000) {
synchronized(lock) { i++ }
}
}
jobs.add(job)
}
jobs.joinAll()
println("i = $i,耗时:${System.currentTimeMillis() - start}")
}
//打印:i = 10000,耗时:73
4.2.3 リエントラントロックの使用 ReenTrantLock
fun main() = runBlocking {
val start = System.currentTimeMillis()
val lock = ReentrantLock()
var i = 0
val jobs = mutableListOf<Job>()
repeat(10) {
val job = launch(Dispatchers.Default) {
repeat(1000) {
lock.lock()
i++
lock.unlock()
}
}
jobs.add(job)
}
jobs.joinAll()
println("i = $i,耗时:${System.currentTimeMillis() - start}")
}
//打印:i = 10000,耗时:83
4.2.4 AtomicInteger を使用して原子性を確保する
fun main() = runBlocking {
val start = System.currentTimeMillis()
var i = AtomicInteger(0)
val jobs = mutableListOf<Job>()
repeat(10) {
val job = launch(Dispatchers.Default) {
repeat(1000) { i.incrementAndGet() }
}
jobs.add(job)
}
jobs.joinAll()
println("i = $i,耗时:${System.currentTimeMillis() - start}")
}
//打印:i = 10000,耗时:89
4.3 シングルスレッドの使用 (非推奨)
これは、バージョン 1.6 より前に、limitedParallelism() なしで行われていましたが、このアプローチの問題は、閉じるために close() を使用するのを忘れやすく、スレッド プールの使用を相殺する可能性があることです (未使用のスレッドはアクティブに保ちますが、他のスレッドはアクティブに維持しません)。サービスはこれらのスレッドを共有します)。
fun main() = runBlocking {
val start = System.currentTimeMillis()
val mySingleDispatcher = Executors.newSingleThreadExecutor {
Thread(it, "我的线程").apply { isDaemon = true }
}.asCoroutineDispatcher()
var i = 0
val jobs = mutableListOf<Job>()
repeat(10) {
val job = launch(mySingleDispatcher) {
repeat(1000) { i++ }
}
jobs.add(job)
}
jobs.joinAll()
println("i = $i,耗时:${System.currentTimeMillis() - start}")
}
//打印:i = 10000,耗时:64
4.4 ミューテックスの使用
Java メソッドは一時停止関数の呼び出しをサポートしていません。同期ロックはブロックされており、コルーチンの特性に影響します。このため、Kotlin では非ブロック ロックの Mutex が提供されています。マルチスレッド同期は、mutex.lock() と mutex.unlock() を使用して同期が必要な計算ロジックをラップすることで実現できますが、パッケージの内容に例外が発生する可能性があるため、unlock() を実行できません。それをfinally{}で書くのは非常に難しいので、面倒なので拡張関数mutex.withLock{ }を用意してありますが、要はfinally{ }内でunlock()を呼び出すことです。
public stop inline fun <T> Mutex.withLock(owner: Any? = null, action: () -> T): T { lock(owner) try { return action() }finally { // catch がないことに注意してくださいここではコードブロックなので、例外はキャッチされません unlock(owner) } }
|
fun main() = runBlocking {
val start = System.currentTimeMillis()
var i = 0
val mutex = Mutex()
//使用方式一
mutex.lock()
// try {
// repeat(10000) { i++ }
// } catch (e: Exception) {
// e.printStackTrace()
// } finally {
// mutex.unlock()
// }
//使用方式二
mutex.withLock {
try {
repeat(10000) { i++ }
} catch (e: Exception) {
e.printStackTrace()
}
}
println("i = $i,耗时:${System.currentTimeMillis() - start}")
}
//方式一打印:i = 10000,耗时:17
//方式二打印:i = 10000,耗时:17
4.5 アクターの使用
アクターは同時同期モデルであり、基本的にチャネル パイプライン メッセージに基づいて実装されます。
sealed class Msg {
object AddMsg : Msg()
class ResultMsg(val result: CompletableDeferred<Int>) : Msg()
}
@OptIn(ObsoleteCoroutinesApi::class)
fun main() = runBlocking {
val start = System.currentTimeMillis()
val actor = actor<Msg> {
var i = 0
for (msg in channel) {
when (msg) {
is Msg.AddMsg -> i++
is Msg.ResultMsg -> msg.result.complete(i)
}
}
}
val jobs = mutableListOf<Job>()
repeat(10) {
val job = launch {
repeat(1000) {
actor.send(Msg.AddMsg)
}
}
jobs.add(job)
}
jobs.joinAll()
val deferred = CompletableDeferred<Int>()
actor.send(Msg.ResultMsg(deferred))
val result = deferred.await()
actor.close()
println("i = $result,耗时:${System.currentTimeMillis() - start}")
}
//打印:i = 10000,耗时:167
4.6 セマフォの使用
セマフォはコルーチン内のセマフォであり、パス数を 1 に指定することで同時実行数を 1 にすることができます。