在Scala中,人们可以轻松地执行并行映射,forEach等,并使用:
collection.par.map(..)
Kotlin中是否有等同物?
答案 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)
}
这是一个简单的用法示例
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())
}
请注意,要调用async
和await
,您需要在所谓的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()
}