代码之家  ›  专栏  ›  技术社区  ›  pathikrit

Scala部分函数应用语义+同步锁定

  •  0
  • pathikrit  · 技术社区  · 8 年前

    基于 my previous question

    /**
      * An util that provides synchronization using value equality rather than referential equality
      * It is guaranteed that if two objects are value-equal, their corresponding blocks are invoked mutually exclusively.
      * But the converse may not be true i.e. if two objects are not value-equal, they may be invoked exclusively too
      * Note: Typically, no need to create instances of this class. The default instance in the companion object can be safely reused
      *
      * @param size There is a 1/size probability that two invocations that could be invoked concurrently is not invoked concurrently
      *
      * Example usage:
      *   import EquivalenceLock.{defaultInstance => lock}
      *   def run(person: Person) = lock(person) { .... }
      */
    class EquivalenceLock(val size: Int) {
      private[this] val locks = IndexedSeq.fill(size)(new Object())
      def apply[U](lock: Any)(f: => U) = locks(lock.hashCode().abs % size).synchronized(f)
    }
    
    object EquivalenceLock {
      implicit val defaultInstance = new EquivalenceLock(1 << 10)
    }
    

    我编写了一些测试来验证我的锁是否按预期工作:

    import EquivalenceLock.{defaultInstance => lock}
    
    import scala.concurrent.Future
    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.collection.mutable
    
    val journal = mutable.ArrayBuffer.empty[String]
    
    def log(msg: String) = journal.synchronized {
      println(msg)
      journal += msg
    }
    
    def test(id: String, napTime: Int) = Future {
      lock(id) {
        log(s"Entering $id=$napTime")
        Thread.sleep(napTime * 1000L)
        log(s"Exiting $id=$napTime")
      }
    }
    
    test("foo", 5)
    test("foo", 2)
    
    Thread.sleep(20 * 1000L)
    
    val validAnswers = Set(
      Seq("Entering foo=5", "Exiting foo=5", "Entering foo=2", "Exiting foo=2"),
      Seq("Entering foo=2", "Exiting foo=2", "Entering foo=5", "Exiting foo=5")
    )
    
    println(s"Final state = $journal")
    assert(validAnswers(journal))
    

    def apply[U](lock: Any)(f: => U) = locks(lock.hashCode().abs % size).synchronized(f)
    

    至:

    def apply[U](lock: Any) = locks(lock.hashCode().abs % size).synchronized _
    

    测试失败。

    预期:

    Entering foo=5
    Exiting foo=5
    Entering foo=2
    Exiting foo=2
    

    Entering foo=2
    Exiting foo=2
    Entering foo=5
    Exiting foo=5
    

    实际:

    Entering foo=5
    Entering foo=2
    Exiting foo=2
    Exiting foo=5
    

    上述两段代码应该相同,但测试(即 lock(id) 总是同时输入相同的 id )代码的第二种风格(部分应用)。为什么?

    1 回复  |  直到 8 年前
        1
  •  2
  •   pathikrit    8 年前

    默认情况下,会急切地评估函数参数。所以

    def apply[U](lock: Any) = locks(lock.hashCode().abs % size).synchronized _
    

    等于

    def apply[U](lock: Any)(f: U) = locks(lock.hashCode().abs % size).synchronized(f)
    

    在这种情况下,f在同步块之前计算。