Как связывание обещаний работает в Scala и почему это необходимо? - PullRequest
0 голосов
/ 28 сентября 2018

Я нашел несколько статей и исходный код будущего и обещаний Scala:

но я все еще не очень понимаю, как на самом деле связывание обещанияработает.Прежде всего, стресс-тест истощения памяти ссылается на большие массивы в обратных вызовах.Это должно привести к тому, что большие массивы останутся в памяти, пока не будут выполнены обратные вызовы?

Я попытался написать сценарий стресс-теста в псевдокоде.

Пример, показывающий вызов сi = 3:

x4 = call3

call3 = {
    val array3 = new BigArray
    val f3 = Future { 3 }
    x3 = f3.flatMap( 3 => array3; call2 )

    return x3
}

call2 = {
    val array2 = new BigArray
    val f2 = Future { 2 }

    x2 = f2.flatMap( 2 => array2; call1 )

    return x2
}

call1 = {
    val array1 = new BigArray
    val f1 = Future { 1 }

    x1 = f1.flatMap( 1 => array1; call0 )

    return x1
}

call0 = {
    val array0 = new BigArray
    val f0 = Future { 0 }

    x0 = f0.flatMap( 0 => Future.succesful() )

    return x0
}

Обычно x0, x1, x2 и x3 запускаются при завершении f0, f1, f2 и f3 и затем вызывают их функции, например:

1 => array1; call0

так что это вызовет x1.completeWith(call0), что в основном равно x1.completeWith(x0).

Это приведет к следующей цепочке:

x4.completeWith(x3)
x3.completeWith(x2)
x2.completeWith(x1)
x1.completeWith(x0)
x0.completeWith(Future.successful())

Насколько я понимаю, так как все вызовы приводят ктот же результат, они могут быть связаны как:

x4.completeWith(Future.successful())

Пока все обратные вызовы из x0, x1, x2 и x3 перемещаются в x4.Все фьючерсы / обещания становятся идентичными?

Теперь, как связывание обещаний ведет себя точно в Scala 2.13.xx?Является ли x4 корнем, который ожидает завершения других фьючерсов?Другие фьючерсы / обещания теперь конвертируются в Link [T]?Кажется, что метод linkRootOf в реализации Scala 2.13.xx создает новую ссылку на цель и сохраняет ее в состоянии будущее / обещание.Он заменяет свои обратные вызовы им.Обратные вызовы перемещаются в корневую цель будущего / обещания, которая будет выполнена, когда корневое будущее / обещание завершено?Это происходит, когда f0 завершено.

Даже если есть цепочка ссылок на корневое будущее / обещание, я не понимаю, почему он больше не течет?

Ссылки избольшие массивы доступны, когда f0, f1, f2 и f3 завершены с тех пор, как выполняются обратные вызовы.Но разве это не время, когда ссылки создаются, поэтому ссылки уже ушли?completeWith не должно быть никаких блокировок, чтобы массивы могли быть освобождены даже без создания ссылки?

1 Ответ

0 голосов
/ 16 ноября 2018

Поскольку никто еще не ответил на этот вопрос, я постараюсь ответить на него сам.Я спросил на форуме Scala о связывании обещаний и получил объяснение: https://users.scala -lang.org / t / how-do-обещание-linking-work / 3326

От моегопонимание того, что это работает, только когда обещания / фьючерсы идентичны, как я и подозревал

Исчерпание памяти из-за ссылки на массив в замыкании, которое не было освобождено сборщиком мусора, было просто ошибкой в ​​компиляторе Scala.Таким образом, из-за этой ошибки вся оптимизация является своего рода чрезмерным проектированием.Я не смог воспроизвести исчерпание памяти в последней версии Scala.

Однако уменьшение длины цепочки будет работать при связывании обещаний, и поэтому будет меньше обещаний, если они идентичны, но поскольку обещание не должно принимать этомного памяти, оптимизация на самом деле не нужна.Я не смог найти вариант использования такого количества вложенных вызовов flatMap.

Из того, что я прочитал в реализациях Scala FP и Twitter Util для примера, который я разместил в своем вопросе, ссылки будутпроизводится следующим образом:

x4.completeWith(x3) // x3 becomes a link to x4 and moves all of its callback to x4
x3.completeWith(x2) // x2 becomes a link to x3. Since x3 is a link to x4, all the callbacks are moved to x4. When the compression of the chain takes place it is directly linked to x4, so x3 can be released by the garbage collection since there is no reference to it anymore.
x2.completeWith(x1) // x1 becomes a link to x2 and by compression directly to x4
x1.completeWith(x0) // x0 becomes a link to x1 and by compression directly to x4
x0.completeWith(Future.successful()) // the passed future completes x0 since it is already completed and since x0 is a link to x4 it will actually complete x4 and all callbacks will be submitted to the underlying executor

Сжатие цепочки ссылок позволяет раньше освобождать элементы ссылки при сборке мусора, поскольку они больше нигде не упоминаются.Это означает, что у вас меньше обещаний в памяти.Всегда будет только x4 и одна ссылка на него.Обратные вызовы собраны в корневом обещании / будущем x4.Нам следует предположить, что x3, x2, x1 и x0 имеют некоторые обратные вызовы, зарегистрированные до вызова tryCompleteWith.В противном случае ничего не будет перемещено.

...