并行运行while循环

时间:2020-05-11 12:10:45

标签: libktx

我有一个大型集合(+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>())

我对其进行了几次测试,结果如下:

  • 1个线程-21213ms
  • 5个线程-27894ms
  • 10个线程-21494ms
  • 15个线程-20986ms

我做错了什么?

1 个答案:

答案 0 :(得分:1)

  1. 您正在使用Java标准库中的Collections.synchronizedList,该库返回一个列表包装器,该包装器利用阻塞synchronized机制来确保线程安全。该机制与协程不兼容,因为它阻止其他线程访问集合,直到操作完成。从多个协程访问数据时,通常应使用非阻塞并发集合,或使用non-blocking mutex保护共享数据。

  2. 随着添加的元素越来越多,
  3. List.contains将变得越来越慢(O(n))。您应该使用visitedFieldsIndexes的集合而不是列表。只要确保使用互斥锁保护它或使用并发变量即可。同样,从availableFieldsWrappers中删除带有随机索引的值非常昂贵-相反,您可以将列表随机洗一次并使用简单的迭代。

  4. 您没有重用协程上下文。通常,您可以一次创建异步上下文并重用其实例,而不必在每次需要协程时都创建一个新的线程池。您应该只调用和分配newAsyncContext(10)的结果一次,然后在整个应用程序中重复使用它。

  5. 您当前编写的代码不能很好地利用协程。与其将协程分派器视为一个线程池,在线程池中您可以并行启动N个大任务(即您的while availableFieldsWrappers.isNotEmpty循环),不如将其视为执行数百或数千个小任务的执行者,并调整代码相应地。我认为您可以通过引入例如的代码来重写代码,从而完全避免 available / visited 集合。 Kotlin flows或仅处理多个逻辑部分的多个KtxAsync.async / KtxAsync.launch调用。

  6. 除非某些函数正在挂起或在下面使用协程,否则您根本就不会真正利用异步上下文的多个线程。 withContext(newAsyncContext(10))启动一个协程,该协程仅利用一个线程就可以顺序处理整个逻辑。有关如何重写代码的一些想法,请参见4.。尝试收集(或仅打印)线程散列和名称,以查看是否所有线程都使用得很好。

相关问题