Вы теряете записи, потому что i++
не является атомарной операцией - значение должно быть прочитано, увеличено, а затем записано обратно - и у вас есть несколько потоков, читающих и пишущих i
одновременно. (Если вы не предоставляете launch
контексту, он по умолчанию использует пул потоков.)
Вы теряете 1 из своего счетчика каждый раз, когда два потока читают одно и то же значение, поскольку они оба будут записывать это значение плюс один.
Синхронизация каким-либо образом, например, с помощью AtomicInteger
решает это:
fun main(args: Array<String>) {
val i = AtomicInteger(0)
val range = (1..100000)
range.map {
launch {
i.incrementAndGet()
}
}
println("$i ${range.count()}") // 100000 100000
}
Также нет гарантии, что эти фоновые потоки будут выполнены с их работой к тому времени, когда вы напечатаете результат и ваша программа завершится - вы можете легко проверить это, добавив очень маленькую задержку в launch
, пару миллисекунд. При этом, это хорошая идея, чтобы обернуть все это в вызов runBlocking
, который поддержит основной поток, а затем дождаться завершения сопрограмм:
fun main(args: Array<String>) = runBlocking {
val i = AtomicInteger(0)
val range = (1..100000)
val jobs: List<Job> = range.map {
launch {
i.incrementAndGet()
}
}
jobs.forEach { it.join() }
println("$i ${range.count()}") // 100000 100000
}