Kotlin系列的并行操作?

时间:2016-01-09 19:18:41

标签: parallel-processing kotlin

在Scala中,人们可以轻松地执行并行映射,forEach等,并使用:

collection.par.map(..)

Kotlin中是否有等同物?

9 个答案:

答案 0 :(得分:35)

Kotlin标准库不支持并行操作。但是,由于Kotlin使用标准Java集合类,因此您也可以使用Java 8流API在Kotlin集合上执行并行操作。

e.g。

myCollection.parallelStream()
        .map { ... }
        .filter { ... }

答案 1 :(得分:29)

从Kotlin 1.1开始,并行操作也可以用coroutines表达得非常优雅。以下是列表中的pmap

fun <A, B>List<A>.pmap(f: suspend (A) -> B): List<B> = runBlocking {
    map { async(CommonPool) { f(it) } }.map { it.await() }
}

请注意,协同程序仍然是一个实验性功能。

答案 2 :(得分:11)

Kotlin的stdlib尚未获得官方支持,但您可以定义extension function 来模仿par.map

fun <T, R> Iterable<T>.pmap(
          numThreads: Int = Runtime.getRuntime().availableProcessors() - 2, 
          exec: ExecutorService = Executors.newFixedThreadPool(numThreads),
          transform: (T) -> R): List<R> {

    // default size is just an inlined version of kotlin.collections.collectionSizeOrDefault
    val defaultSize = if (this is Collection<*>) this.size else 10
    val destination = Collections.synchronizedList(ArrayList<R>(defaultSize))

    for (item in this) {
        exec.submit { destination.add(transform(item)) }
    }

    exec.shutdown()
    exec.awaitTermination(1, TimeUnit.DAYS)

    return ArrayList<R>(destination)
}

github source

这是一个简单的用法示例

val result = listOf("foo", "bar").pmap { it+"!" }.filter { it.contains("bar") }

如果需要,它允许通过提供线程数甚至特定java.util.concurrent.Executor来调整线程。 E.g。

listOf("foo", "bar").pmap(4, transform = { it + "!" })

请注意,此方法只允许并行化map操作,不会影响任何下游位。例如。第一个示例中的filter将运行单线程。但是,在许多情况下,只是数据转换(即map)需要并行化。此外,将方法从上面扩展到Kotlin集合API的其他元素将是直截了当的。

答案 3 :(得分:9)

从1.2版本开始,kotlin添加了stream feature,它符合JRE8

因此,可以像下面这样来异步遍历列表:

fun main(args: Array<String>) {
  val c = listOf("toto", "tata", "tutu")
  c.parallelStream().forEach { println(it) }
}

答案 4 :(得分:4)

目前没有。与Scala的官方Kotlin比较提到:

  

后来可能添加到Kotlin的事情:

     
      
  • 并行馆藏
  •   

答案 5 :(得分:3)

我发现另一种非常优雅的方法是使用 kotlinx.coroutines 库:

import kotlinx.coroutines.flow.asFlow

suspend fun process(myCollection: Iterable<Foo>) {
    myCollection.asFlow()
        .map { /* ... */ }
        .filter { /* ... */ }
        .collect { /* ... perform some side effect ... */ }
}

然而它确实需要额外的依赖; kotlinx.coroutines 不在标准库中。

答案 6 :(得分:2)

科特林想成为惯用语言,但又不要过多地合成,以使乍一看很难理解。

并行程序中的协程也不例外。他们希望它简单易用,但不希望通过某种预先构建的方法隐式包含,从而允许在需要时分支计算。

在您的情况下:

collection.map { 
        async{ produceWith(it) } 
    }
    .forEach { 
        consume(it.await()) 
    }

请注意,要调用asyncawait,您需要在所谓的Context内部,您不能从非协程环境中进行挂起调用或启动协程。要输入一个,您可以:

  • runBlocking { /* your code here */ }:它将暂停当前线程,直到lambda返回。
  • GlobalScope.launch { }:它将并行执行lambda;如果您的main在协程还不错的情况下完成执行,那么最好使用runBlocking

希望这会有所帮助:)

答案 7 :(得分:1)

您可以使用以下扩展方法:

suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> = coroutineScope {
    map { async { f(it) } }.awaitAll()
}

有关更多信息,请参见Parallel Map in Kotlin

答案 8 :(得分:0)

此解决方案假定您的项目正在使用协程:

implementation( "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2")

名为parallelTransform的函数不会保留元素的顺序并返回Flow<R>,而函数parallelMap会保留元素的顺序并返回List<R>

为多个调用创建线程池:

val numberOfCores = Runtime.getRuntime().availableProcessors()
val executorDispatcher: ExecutorCoroutineDispatcher =
    Executors.newFixedThreadPool(numberOfCores ).asCoroutineDispatcher()

使用该调度程序(并在不再需要时调用close()

inline fun <T, R> Iterable<T>.parallelTransform(
    dispatcher: ExecutorDispatcher,
    crossinline transform: (T) -> R
): Flow<R> = channelFlow {

    val items: Iterable<T> = this@parallelTransform
    val channelFlowScope: ProducerScope<R> = this@channelFlow

    launch(dispatcher) {
        items.forEach {item ->
            launch {
                channelFlowScope.send(transform(item))
            }
        }
    }
}

如果不必担心线程池重用(线程池并不便宜),则可以使用以下版本:

inline fun <T, R> Iterable<T>.parallelTransform(
    numberOfThreads: Int,
    crossinline transform: (T) -> R
): Flow<R> = channelFlow {

    val items: Iterable<T> = this@parallelTransform
    val channelFlowScope: ProducerScope<R> = this@channelFlow

    Executors.newFixedThreadPool(numberOfThreads).asCoroutineDispatcher().use { dispatcher ->
        launch( dispatcher ) {
            items.forEach { item ->
                launch {
                    channelFlowScope.send(transform(item))
                }
            }
        }
    }
}

如果您需要保留元素顺序的版本:

inline fun <T, R> Iterable<T>.parallelMap(
    dispatcher: ExecutorDispatcher,
    crossinline transform: (T) -> R
): List<R> = runBlocking {

    val items: Iterable<T> = this@parallelMap
    val result = ConcurrentSkipListMap<Int, R>()

    launch(dispatcher) {
        items.withIndex().forEach {(index, item) ->
            launch {
                result[index] = transform(item)
            }
        }
    }

    // ConcurrentSkipListMap is a SortedMap
    // so the values will be in the right order
    result.values.toList()
}