我有一个大型集合(+90 000个对象),我想同时在其上循环运行,我的函数源在下面
val context = newSingleThreadAsyncContext()
return KtxAsync.async(context) {
val fields = regularMazeService.generateFields(colsNo, rowsNo)
val time = measureTimeMillis {
withContext(newAsyncContext(10)) {
while (availableFieldsWrappers.isNotEmpty()) {
val wrapper = getFirstShuffled(availableFieldsWrappers.lastIndex)
.let { availableFieldsWrappers[it] }
if (wrapper.neighborsIndexes.isEmpty()) {
availableFieldsWrappers.remove(wrapper)
continue
}
val nextFieldIndex = getFirstShuffled(wrapper.neighborsIndexes.lastIndex)
.let {
val fieldIndex = wrapper.neighborsIndexes[it]
wrapper.neighborsIndexes.removeAt(it)
fieldIndex
}
if (visitedFieldsIndexes.contains(nextFieldIndex)) {
wrapper.neighborsIndexes.remove(nextFieldIndex)
fields[nextFieldIndex].neighborFieldsIndexes.remove(wrapper.index)
continue
}
val nextField = fields[nextFieldIndex]
availableFieldsWrappers.add(FieldWrapper(nextField, nextFieldIndex))
visitedFieldsIndexes.add(nextFieldIndex)
wrapper.field.removeNeighborWall(nextFieldIndex)
nextField.removeNeighborWall(wrapper.index)
}
}
}
Gdx.app.log("maze-time", "$time")
在课堂上
private val availableFieldsWrappers = Collections.synchronizedList(mutableListOf<FieldWrapper>())
private val visitedFieldsIndexes = Collections.synchronizedList(mutableListOf<Int>())
我对其进行了几次测试,结果如下:
我做错了什么?
答案 0 :(得分:1)
您正在使用Java标准库中的Collections.synchronizedList
,该库返回一个列表包装器,该包装器利用阻塞synchronized
机制来确保线程安全。该机制与协程不兼容,因为它阻止其他线程访问集合,直到操作完成。从多个协程访问数据时,通常应使用非阻塞并发集合,或使用non-blocking mutex保护共享数据。
List.contains
将变得越来越慢(O(n))。您应该使用visitedFieldsIndexes
的集合而不是列表。只要确保使用互斥锁保护它或使用并发变量即可。同样,从availableFieldsWrappers
中删除带有随机索引的值非常昂贵-相反,您可以将列表随机洗一次并使用简单的迭代。
您没有重用协程上下文。通常,您可以一次创建异步上下文并重用其实例,而不必在每次需要协程时都创建一个新的线程池。您应该只调用和分配newAsyncContext(10)
的结果一次,然后在整个应用程序中重复使用它。
您当前编写的代码不能很好地利用协程。与其将协程分派器视为一个线程池,在线程池中您可以并行启动N个大任务(即您的while availableFieldsWrappers.isNotEmpty
循环),不如将其视为执行数百或数千个小任务的执行者,并调整代码相应地。我认为您可以通过引入例如的代码来重写代码,从而完全避免 available / visited 集合。 Kotlin flows或仅处理多个逻辑部分的多个KtxAsync.async
/ KtxAsync.launch
调用。
除非某些函数正在挂起或在下面使用协程,否则您根本就不会真正利用异步上下文的多个线程。 withContext(newAsyncContext(10))
启动一个协程,该协程仅利用一个线程就可以顺序处理整个逻辑。有关如何重写代码的一些想法,请参见4.。尝试收集(或仅打印)线程散列和名称,以查看是否所有线程都使用得很好。