1) Если для каждого потока создается свой собственный стек в памяти, то при создании пула потоков на весь пул создается свой собственный стэк?
2) Создавая корутину, мы устанавливаем диспатчер, который определяет контекст выполнения корутины, но если при запуске приложения система создает лишь основной main поток, то как корутина начинает работать в разных потоках, при наличии только main потока?
3) Поскольку протопоток это облегченный поток без стэка представляющий линейное выполнение кода с возможностью приостановления выполнения функции, то могу ли я сказать, что корутина без стэка это протопоток? Или они просто сильно похожи?
4) Чем отличаются корутина без стэка и со стэком? (Не смог понятно для себя перевести)
5) На одну корутину выделяется 2 байта?
6) Цена расхода блокирующегося контекста корутины равна 2 байтам?
1) откуда ты это взял?
2) а в коде посмотреть? Система ничего не знает про корутины.
3) что такое протопоток?
4) какие ещё корутины со стеком и без стека?
5) чего?
6) чегоооо?
Денис Загаевский,
Не просто было восстановить всю хронологию моего поиска в инете чтобы вам было удобнее понять о чем я спрашиваю, но этот вопрос меня сильно заинтересовал, и очень интересно было бы от вас услышать ответы на мои вопросы, поскольку кроме вас похоже тут мало кто разбирается.
1) На сайте https://metanit.com/java/tutorial/8.1.php
написано следующее:
Недостатки при использовании потоков
Далее мы рассмотрим, как создавать и использовать потоки. Это довольно легко.
Однако при создании многопоточного приложения нам следует учитывать ряд обстоятельств, которые негативно могут сказаться на работе приложения.
На некоторых платформах запуск новых потоков может замедлить работу приложения. Что может иметь большое
значение, если нам критичная производительность приложения.Для каждого потока создается свой
собственный стек в памяти, куда помещаются все локальные переменные и
ряд других данных, связанных с выполнением потока. Соответственно, чем больше потоков создается, тем больше
памяти используется. При этом надо помнить, в любой системе размеры используемой памяти ограничены. Кроме
того, во многих системах может быть ограничение на количество потоков. Но даже если такого ограничения нет, то в любом случае имеется естественное ограничение в виде максимальной скорости процессора.
В ней ей строка "Один из используемых подходов реализации сопрограмм в языках без их встроенной поддержки — не использующие стек протопотоки, обеспечивающие блокирующий контекст ценой расхода нескольких байтов памяти на один поток." Где перечислены несколько языков, включая Kotlin.
foonfyrick, ппц. Ты определись, ты изучаешь корутины в котлине и детали их реализации, или ты изучаешь абстрактную теорию, которая нигде не реализована, и похожие фичи во всех языках и фреймворках?
Денис Загаевский, У меня не получилось разобраться как создается поток для корутины. Я все дебагером вдоль и поперек прошел, я либо смотрю и не понимаю, либо ищу вообще не там, не могу понять, раз изначально приложение запускается с одним потоком main, как корутина создает для себя пул потоков? Если есть выбор, в каких потоках стартовать корутину, но при этом поток изначально один, значит должны какие-то потоки создаться для корутины.
foonfyrick, Ну да, их создает библиотека kotlinx. Дебаггер тут не нужен.
Что тебе известно? Я уже тебе сказал, что для выполнения на другом потоке проще всего использовать Dispatchers.Default. Ну, прямо в IDE же доступны исходники, заходишь и смотришь, что это:
public actual object Dispatchers {
/**
* The default [CoroutineDispatcher] that is used by all standard builders like
* [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc
* if no dispatcher nor any other [ContinuationInterceptor] is specified in their context.
*
* It is backed by a shared pool of threads on JVM. By default, the maximal level of parallelism used
* by this dispatcher is equal to the number of CPU cores, but is at least two.
* Level of parallelism X guarantees that no more than X tasks can be executed in this dispatcher in parallel.
*/
@JvmStatic
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
Это объект, у которого пропертя, которая просто создается вызовом функции. Cmd+Click по ней(у меня такой хоткей, не знаю, как у тебя).
Окай, смотрим
internal fun createDefaultDispatcher(): CoroutineDispatcher =
if (useCoroutinesScheduler) DefaultScheduler else CommonPool
useCoroutinesScheduler описана немного выше, это системная проперти:
internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
when (value) {
null, "", "on" -> true
"off" -> false
else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'")
}
}
не вдаваясь в подробности, у тебя будет либо DefaultScheduler, либо CommonPool. Можно посмотреть оба, или выяснить значение этой проперти(я, например, сходу не скажу, какое она имеет значение).
никаких осмысленно переопределенных методов нет, значит вся соль в его предке. Cmd+click по ExperimentalCoroutineDispatcher.
@InternalCoroutinesApi
public open class ExperimentalCoroutineDispatcher(
private val corePoolSize: Int,
private val maxPoolSize: Int,
private val idleWorkerKeepAliveNs: Long,
private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
public constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE,
schedulerName: String = DEFAULT_SCHEDULER_NAME
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)
@Deprecated(message = "Binary compatibility for Ktor 1.0-beta", level = DeprecationLevel.HIDDEN)
public constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS)
override val executor: Executor
get() = coroutineScheduler
// This is variable for test purposes, so that we can reinitialize from clean state
private var coroutineScheduler = createScheduler()
override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
try {
coroutineScheduler.dispatch(block)
} catch (e: RejectedExecutionException) {
// CoroutineScheduler only rejects execution when it is being closed and this behavior is reserved
// for testing purposes, so we don't have to worry about cancelling the affected Job here.
DefaultExecutor.dispatch(context, block)
}
override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit =
try {
coroutineScheduler.dispatch(block, tailDispatch = true)
} catch (e: RejectedExecutionException) {
// CoroutineScheduler only rejects execution when it is being closed and this behavior is reserved
// for testing purposes, so we don't have to worry about cancelling the affected Job here.
DefaultExecutor.dispatchYield(context, block)
}
override fun close(): Unit = coroutineScheduler.close()
override fun toString(): String {
return "${super.toString()}[scheduler = $coroutineScheduler]"
}
Много букв, но в общем, в глаза бросается executor. Если посмотреть, кого он оверрайдит, там написано
/**
* Underlying executor of current [CoroutineDispatcher].
*/
public abstract val executor: Executor
То есть это тот executor, на котором непосредственно всё будет выполняться, что ты запустишь на этом диспатчере.
Дальше, естественно, смотрим private var coroutineScheduler = createScheduler()
Cmd+click
private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
nternal class CoroutineScheduler(
@JvmField val corePoolSize: Int,
@JvmField val maxPoolSize: Int,
@JvmField val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
@JvmField val schedulerName: String = DEFAULT_SCHEDULER_NAME
) : Executor, Closeable {
Вот это уже непосредственно реализация executor'a.
Здесь можно обнаружить такую пропертю
/**
* State of worker threads.
* [workers] is array of lazily created workers up to [maxPoolSize] workers.
* [createdWorkers] is count of already created workers (worker with index lesser than [createdWorkers] exists).
* [blockingTasks] is count of pending (either in the queue or being executed) tasks
*
* **NOTE**: `workers[0]` is always `null` (never used, works as sentinel value), so
* workers are 1-indexed, code path in [Worker.trySteal] is a bit faster and index swap during termination
* works properly
*/
@JvmField
val workers = AtomicReferenceArray<Worker?>(maxPoolSize + 1)
Worker это уже непосредственно поток:
internal inner class Worker private constructor() : Thread() {
Дальше в этом классе можно копаться и смотреть, что как делают взрослые серьёзные дяди. Там сложный многопоточный низкоуровневый библиотечный код. Но, собственно, не вникая в детали, вот они твои потоки, которые создаются. Абсолютно аналогично в CommonPool можешь посмотреть что происходит
Это всё писать значительно дольше, чем прощёлкать хоткеями по ссылкам в коде и посмотреть, что происходит. Это всё детали реализации, которые могут меняться(но есть комментарии, которые описывают, как оно должно работать).
Дебаггером тоже можно - остановиться на бряке посреди запущенной корутины, и посмотреть, на каком треде происходит дело. Ну, будет реализация какого-то воркера, собственно.
foonfyrick, Короче, чтобы перестать задавать такие вопросы, нужно приобрести привычку читать код. И научиться его читать, отбрасывая ненужные подробности, чтобы вникнуть в суть. Единственный способ этому научиться - читать код. Постепенно научишься.
Денис Загаевский, спасибо, я в ExperimentalCoroutineDispatcher как раз и не заглядывал, я на название посмотрел и подумал, раз это что-то эксперементальное, то значит там что-то временное и не стоящее внимания