Как реализован стек асинхронных вызовов

#function #compiler-construction #callstack

#функция #компилятор-построение #callstack

Вопрос:

Допустим, у вас есть эта последовательность функций (JavaScript)….

 A(function(){
  console.log('done')
})

function A(done) {
  a()
  B(D, done)
}

function B(x, y) {
  x(function(){
    c()
    C(y)
    d()
  })
}

function C(z) {
  g()
  setTimeout(z, 1000)
}

function D(z) {
  h()
  setTimeout(z, 2000)
}

function a() {
  b()
  c()
}

function b() {
  // ... sync stuff
}

function c() {
  e()
  // ... sync stuff
  f()
}

function d() {
  // ... sync stuff
}
  

Попытался сделать так, чтобы у него был своего рода сложный стек вызовов.

Мне интересно, как выглядит стек вызовов в разные моменты времени. Например, c();C(y);d() последовательность. Когда c() вызывается, следующей функцией, которая будет вызвана на этом уровне, является C() . Таким образом, похоже, что он будет помещен в стек (перед оценкой c() ), который C() является местом возврата. Затем он переходит к e() и f() (игнорируя это на данный момент). Затем он проверяет стек вызовов и возвращается к C() . Затем тот же процесс. Но поскольку C() является асинхронным, оно выполняется d() до C() завершения. Итак, это так:

 c   c   c   c   c   c     c    ...?
    C   C   C   C   C    /  
        e   e   f       C    d
            f
  

Вот о чем я думаю, когда пытаюсь отобразить стек вызовов. Похоже, что это сформирует дерево. Теперь представьте, что несколько асинхронных процессов запускаются примерно в одно и то же время. Тогда это похоже на несколько ветвей дерева. Итак, вместо стека вызовов, дерево вызовов. Это заставляет меня, наконец, задаться вопросом, как именно оценивается стек вызовов. Когда следующая функция в последовательности помещается в стек вызовов и как они обновляют / удаляют последнюю завершенную функцию и находят обратный путь к следующему месту в стеке / дереве вызовов.

Интересно, не могли бы вы указать какие-либо ресурсы, которые могли бы описать это, или, возможно, даже объяснить, как будет выглядеть стек вызовов в примере, который я описал выше.

Комментарии:

1. Это действительно не вопрос сборки. Не думайте об этом как об одном стеке вызовов. Представьте себе асинхронное выполнение в виде нескольких стеков вызовов в потоках.

Ответ №1:

Когда вы вызываете функцию, вы помещаете адрес возврата в стек, а не следующую вызываемую функцию. Затем вызываемая функция создаст свой собственный фрейм в стеке (вы можете считать адрес возврата частью этого фрейма или отдельным от фрейма, в зависимости от того, как вы на это смотрите. Когда функция возвращается, она выводит свой фрейм и возвращается к адресу возврата (который также неявно или явно выводит адрес возврата — детали зависят от архитектуры процессора / виртуальной машины.

Итак, для вашего примера стек вызовов с течением времени больше похож

 c c c c c C C
  e   f     g
  

Комментарии:

1. Но, похоже, адрес возврата — это следующая функция, я не вижу, чем она отличается. А также не уверен, что происходит в случае асинхронности.

2. Нет, адрес возврата находится в середине текущей функции. Следующая вещь в текущей функции в этом случае — это другой вызов (следующей функции), но это может быть что угодно.