mogę się mylić, ale czynność nie musi być ogon rekurencyjnej w tym przypadku.
Rekursja ogona pomaga nam nie zużywać stosu w przypadku korzystania z funkcji rekursywnych. W twoim przypadku jednak nie zużywamy stosu w taki sposób, jak typowa funkcja rekursywna.
Dzieje się tak dlatego, że wywołanie "rekursywne" nastąpi asynchronicznie, w jakimś wątku z kontekstu wykonania. Jest więc bardzo prawdopodobne, że to wywołanie rekurencyjne nie będzie nawet znajdować się na tym samym stosie, co pierwsze wywołanie.
Metoda factorialAcc
utworzy przyszły obiekt, który w końcu asynchronicznie wywoła wywołanie "rekurencyjne". Następnie jest natychmiast wyskakiwany ze stosu.
Więc to nie jest rekursja stosu, a stos nie rośnie proporcjonalnie do n, pozostaje mniej więcej w stałym rozmiarze.
Możesz to łatwo sprawdzić, rzucając wyjątek w pewnym momencie metody factorialAcc
i sprawdzając ślad stosu.
I przepisał swój program, aby uzyskać bardziej czytelny ślad stosu:
object Main extends App {
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
implicit val ec = scala.concurrent.ExecutionContext.global
def factorialAcc(acc: Int, n: Int): Future[Int] = {
if (n == 97)
throw new Exception("n is 97")
if (n <= 1) {
Future.successful(acc)
} else {
val fNum = getFutureNumber(n)
fNum.flatMap(num => factorialAcc(num * acc, num - 1))
}
}
def factorial(n: Int): Future[Int] = {
factorialAcc(1, n)
}
protected def getFutureNumber(n: Int) : Future[Int] = Future.successful(n)
val r = Await.result(factorial(100), 5.seconds)
println(r)
}
a wyjście jest:
Exception in thread "main" java.lang.Exception: n is 97
at test.Main$.factorialAcc(Main.scala:16)
at test.Main$$anonfun$factorialAcc$1.apply(Main.scala:23)
at test.Main$$anonfun$factorialAcc$1.apply(Main.scala:23)
at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:278)
at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:274)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:29)
at scala.concurrent.impl.ExecutionContextImpl$$anon$3.exec(ExecutionContextImpl.scala:107)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:262)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:975)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1478)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:104)
Więc widać, że stos jest rzeczywiście krótki. Jeśli był to rekurencja stosu, powinieneś zobaczyć około 97 wywołań do metody factorialAcc
. Zamiast tego widzisz tylko jeden.
Myślę, że ta odpowiedź będzie mi odpowiadała za to, czego potrzebuję, jednak nadal istnieje niewielki wyciek pamięci, ponieważ scala.Współbieżne Futures nie łączą się. Ale twitter Futures działa poprzez jakieś czary. Mówiąc to, musisz zgłosić DEEP, zanim to się zawiesi. głębiej niż jest to realistyczne, więc jestem z tego zadowolony. – Donuts
Badam coś podobnego [tutaj] (http://stackoverflow.com/questions/30039893/how-do-i-mitigate-memory-leaks-when-recursive-calling-a-function-inside-a-futu) przy użyciu biblioteki klienta Google Play. Czy masz lepszy wgląd w problem z wyciekiem pamięci? – nmurthy
W jaki sposób stos będzie mniej więcej w stałym rozmiarze? czy ogólny rozmiar nie byłby po prostu rozprowadzany między różnymi wątkami? – matanster