Leistung von Mutlitheading in RX vs Theads vs Executors


8

Ich schreibe eine Backend-Anwendung in Kotlin.

Um die Arbeit zu beschleunigen, verlasse ich mich derzeit auf RxKotlin auf dem Server, um E / A-Aufgaben wie Datenbankaufrufe und API-Aufrufe parallel auszuführen. Der Code sieht normalerweise so aus.

val singleResult1 = Single.fromCallable{
  database.get(....)
}.io()

val singleResult2 = Single.fromCallable{
  database.update(....)
}.io()

Single.zip(singleResult1, singleResult2){ result1: Result1, result2: Result2 ->
    ....
}
.flatMap{
  //other RX calls
}
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.blockingGet()

Da jedoch nicht wirklich mit mehreren Ereignissen (nur Singles) funktioniert, fühlt sich Rx etwas chaotisch an und fügt nur ein paar Boilerplates hinzu (es verursacht auch Komplikationen, wenn ich einen Nullwert zurückgeben möchte und manchmal den Stack-Trace durcheinander bringen könnte )

Ich denke darüber nach, Rx zu entfernen und Executorsstattdessen (oder Threads) für Parallelität zu verwenden. Gibt es hier Leistungsüberlegungen zu berücksichtigen?

Beispiel, woran ich denke:

fun <T> waitAll(tasks: List<Callable<T>>, threadCount: Int = -1): List<T> {
    val threads = if (threadCount == -1) tasks.size else threadCount
    val executor = Executors.newFixedThreadPool(threads)
    val results = executor.invokeAll(tasks).map {
        it.get()
    }
    executor.shutdown()
    return results
}

Und wie folgt:

waitAll(listOf(callable1, callable2))

Oder vielleicht normale Threads verwenden und sie verbinden?

threads.forEach{
   it.start()
}
threads.forEach{
   it.join()
}

Oder warum nicht Streams?

listOf(callable1,callable2)
.parallelStream()
.map{it.call()}
.collect(Collectors.toList())

1
Was du damit meinst: Rx fühlt sich ein bisschen chaotisch an und fügt nur ein paar Boilerplates hinzu (es verursacht auch Komplikationen, wenn ich einen Nullwert zurückgeben möchte und manchmal den Stack-Trace durcheinander bringen könnte) ?
Blasen

1
Gehen Sie aus Liebe zu Gott nicht den Fadenweg. Sie möchten eine ordnungsgemäße Aufgabenabstraktion und Threads werden sehr schnell sehr chaotisch. Die Stream-Route sieht vielversprechender aus, wenn Sie keine Einschränkungen treffen. Ich würde RxKotlin wahrscheinlich durch Coroutinen ersetzen, wenn die Dinge komplizierter werden: Es hat den gleichen Umfang wie RxKotlin und ist nicht auf die Benutzeroberfläche beschränkt, wie Sie es woanders geschrieben haben, aber es fühlt sich idiomatischer an.
Arvid Heise

Antworten:


4

KotlinRx selbst verwendet Executoren, außer dass es zwei vorgefertigte Thread-Pools unter Schedulers.io()(unbegrenzt) und Schedulers.computation()(begrenzt durch die Anzahl der Kerne) hat und nicht jedes Mal einen neuen hochfährt, wie es Ihr vorgeschlagener Code tut. Was Sie natürlich auch manuell tun können:

private val executor = Executors.newCachedThreadPool() // equivalent to io()

fun <T> waitAll(tasks: List<Callable<T>>): List<T> {
    return executor.invokeAll(tasks).map {
        it.get()
    }
}

Dies sollte im Allgemeinen besser sein als das Erstellen eines Threads für jede Aufgabe, indem vorhandene Threads wiederverwendet werden können, hängt jedoch von Ihrer Verwendung ab.

coroutines ist (IMO) besser geeignet für den Umgang mit Hintergrund- / UI-Thread-Kommunikation (dh Android usw.)

Ob Coroutinen dafür sehr nützlich sind, hängt davon ab, was Sie in Ihren Aufgaben haben. Eine blockierende API (zB JDBC)? Sie sind nicht. Eine asynchrone (zB Nachrüstung)? Sie sind.


5

Java Executor-Dienste verwenden Threads und RxKotlin verwendet ExecutorServices. Alle diese sind also im Hintergrund gleich. Der Unterschied liegt in der Softwarearchitektur. Wenn Sie also die beste Architektur auswählen, die in Ihren Code integriert werden soll, funktioniert sie am besten und erledigt die Aufgabe korrekt. Einfach ist das Beste.

Wenn Sie eine ereignisbasierte oder beobachtbare Architektur haben und versuchen, eine neue Bibliothek für die Arbeit mit ereignisbasierten Vorgängen oder Jobs zu implementieren, können Sie falsche Schritte zur Jobtrennung oder zum Timing schreiben. Verwenden Sie RxKotlin und erfinden Sie das Rad nicht erneut.

Wenn es bei Ihrer Arbeit nicht um Ereignisse oder beobachtbare Muster geht und Sie nur parallele Jobs ausführen müssen, verwenden Sie einfach die Executor-Dienste. Der RxKotlin wird überarbeitet. Wenn Sie RxKotlin in dieser Situation verwenden, müssen Sie mehr tun, als Sie benötigen.

Ich denke, die Frage ist nicht die Geschwindigkeit in dieser Situation, sondern die Architektur.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.