#kotlin #concurrency
#kotlin #параллелизм
Вопрос:
У меня есть эти два фрагмента кода kotlin для измерения времени запуска потоков.
Первый:
import kotlinx.coroutines.*
fun main() {
runBlocking {
for(i in 0..99){
val start = System.nanoTime()
launch {
val time = System.nanoTime() - start
println("Starttime: %,d".format(time))
}
}
}
}
Этот код начинается с печати чего-то в диапазоне 35.000.000 и проходит через 100итераций, где значение становится больше и заканчивается около 75.000.000.
Теперь, если я запущу этот код
import kotlinx.coroutines.*
fun main() {
runBlocking {
val list:MutableList<Long> = MutableList<Long>(100, {0})
for(i in 0..99){
val start = System.nanoTime()
launch {
list[i] = System.nanoTime() - start
}
}
delay(1000) // wait for all coroutines to have stored result
list.forEach{ println("Starttime: %,d".format(it)) }
}
}
Этот код, очевидно, быстрее, поскольку оператор печати не включен в запуск. Но есть и другое странное поведение. Он начинается около 50.000.000, переходит непосредственно к 17.083.000, а затем медленно поднимается обратно до 29.507.300, где и заканчивается.
Итак, мои вопросы: почему первый фрагмент кода так быстро ухудшается и быстро замедляется, в то время как второй код может быть намного быстрее и не замедляется со временем, когда я добавляю больше сопрограмм?
Ответ №1:
Вы допускаете небольшую ошибку, не загружая классы перед измерением времени, поэтому результаты искажаются. Вы также должны распечатать индекс заданий, чтобы вы могли видеть, в каком порядке фактически выполняются задания.
Я бы предложил изменить ваши примеры кода:
Первый:
suspend fun main() {
// dummy calls to preload classes before measuring
val s = System.nanoTime()
GlobalScope.launch { println("starting %,d".format(s)) }.join()
repeat(100) { i ->
val start = System.nanoTime()
GlobalScope.launch {
val time = System.nanoTime() - start
println("Starttime[$i]: %,d".format(time))
}
}
delay(1000) // wait for all coroutines to print result
}
Второй:
suspend fun main() {
// dummy calls to preload classes before measuring
val s = System.nanoTime()
GlobalScope.launch { println("starting %,d".format(s)) }.join()
val list: MutableList<Long> = MutableList<Long>(100, { 0 })
repeat(100) { i ->
val start = System.nanoTime()
GlobalScope.launch {
list[i] = System.nanoTime() - start
}
}
delay(1000) // wait for all coroutines to have stored result
list.forEachIndexed { i, it -> println("Starttime[$i]: %,d".format(it)) }
}
Первый образец, очевидно, «ухудшается», поскольку вы просто мгновенно накапливаете 100 заданий, в то время как для доступа к ним требуется значительное количество времени для System.out
выполнения операции печати. Вы также можете столкнуться с тем, что они, скорее всего, не печатают [их индекс] по порядку.
Второй пример более или менее стабилен, потому что задания не выполняют никаких тяжелых операций, поэтому в пуле обычно есть свободный поток Dispatchers.Default
для немедленного выполнения блока запуска.
Комментарии:
1. Спасибо! но когда я не указываю диспетчера, разве все это не выполняется только в основном потоке?
2. @n00bster нет,
launch
используетсяDispatchers.Default
по умолчанию. Запуск в основном потоке не имеет смысла, поскольку он просто блокируется.3. хм, но если я включу печать Thread.currentThread в свою печать, запуск без какого-либо диспетчера будет указывать mainthread, в то время как Dispatchers.default будет чередоваться между потоками?
4. Если вы используете
launch
его как дочернийrunBlocking
элемент, он наследует свой контекст сопрограммы, поэтому он действительно будет выполняться блокирующим образом.5. итак, в моем примере dispatchers.default — это не то же самое, что запуск без явного диспетчера?