在挂起函数中使用非线程安全mutableMap 是否安全?

问题描述

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

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

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

这是代码的简化版本:

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
  }
}

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

解决方法

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

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

更新

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

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

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

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

如前所述,如果地图将被存储,例如作为一个属性,simpleFunction() 会修改它,并且这个函数会被并行调用多次——然后每次调用都会尝试同时修改它。如果异步操作直接修改 myCallResults 也会有所不同。正如我所说,异步块彼此并行运行,因此它们可以同时修改映射。但由于您只从异步块返回结果,然后从单个协程(从外部作用域)修改映射,因此映射是按顺序访问的,而不是并发访问。

,

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

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...