1

我在 Kotlin 中学习协程,我有一段看起来像这样的代码(见下文)。

我的朋友说 mutableMapOf 是 LinkedHashMap,它不是线程安全的。论点是挂起函数可能由不同的线程运行,因此 LinkedHashMap 是不合适的。

  1. 在这里使用简单的可变映射是否安全,或者是否需要 ConcurrentMap?
  2. 当一个挂起函数被挂起时,它可以被另一个线程恢复并执行吗?
  3. 即使(2)是可能的,是否有“发生前/发生后”保证确保所有变量(和底层对象内容)在新线程接管之前与主内存深度同步?

这是代码的简化版本:

class CoroutineTest {

  private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

  suspend fun simpleFunction(): MutableMap<Int,String> { 

    val myCallResults = mutableMapOf<Int,String>()

    val deferredCallResult1 = scope.async {
         //make rest call get string back
    }
    val deferredCallResult2 = scope.async {
         //make rest call get string back
    }
    ...
    myCallResults.put( 1, deferredCallResult1.await() )
    myCallResults.put( 2, deferredCallResult2.await() )
    ...
   
    return myCallResults
  }
}

提前致谢!
PS。我用更多的异步调用结果运行了这段代码,没有问题;所有通话结果都被考虑在内。但这可能是不确定的,这就是我问的原因。

4

2 回答 2

4
  1. mutableMapOf()不,从多个协程中使用一个是不安全的。
  2. 您理解暂停不正确。这不是暂停的功能。函数中运行的协程可能会挂起。从这个角度来看,挂起函数与普通函数并没有真正的不同——它们可以由许多协程同时执行,并且它们都将同时工作。

但是......由于另一个原因,您的代码没有任何问题。这个可变映射是一个局部变量,所以它只对创建它的协程/线程可用。因此,它根本不会被同时访问。如果地图是 - 的属性,情况会有所不同,CoroutineTest那么这可能意味着您需要使用ConcurrentMap.

更新

阅读所有评论后,我相信我对您(或您的朋友)的担忧有了更好的理解,因此我可以提供更准确的答案。

是的,在挂起协程后,它可以从另一个线程恢复,因此协程可以使函数的某些部分由一个线程执行,而另一部分由另一个线程执行。put(1在您的示例中,可能put(2会从两个不同的线程调用。

但是,说它LinkedHashMap不是线程安全的并不意味着它必须始终由同一个线程访问。它可以被多个线程访问,但不能同时访问。一个线程需要完成对地图的更改,然后另一个线程才能执行其修改。

现在,您的代码async { }块可以彼此并行工作。它们也可以与外部作用域并行工作。但是它们每个的内容都是按顺序工作的。put(2line 只能在put(1完全完成后执行,因此不会被多个线程同时访问。

如前所述,如果将映射存储为例如属性、simpleFunction()对其进行修改并且该函数将被并行调用多次 - 然后每次调用将尝试同时修改它,则情况会有所不同。如果异步操作直接修改也会有所不同myCallResults。正如我所说,异步块彼此并行运行,因此它们可以同时修改映射。但是由于您仅从异步块返回结果,然后从单个协程(从外部范围)修改映射,因此映射是按顺序访问的,而不是同时访问的。

于 2021-06-16T21:09:03.060 回答
4

由于映射对于挂起函数是本地的,因此使用非线程安全实现是安全的。不同的线程可能会在不同的挂起函数调用(在这种情况下是await()调用)之间使用映射,但在挂起函数中保证发生之前/之后发生。

如果您的地图是在挂起函数之外声明并通过属性访问的,那么可能会同时调用此函数并且您将同时修改它,这将是一个问题。

于 2021-06-16T21:17:02.787 回答