2016-09-07 3 views
0

Basé sur my previous question sur les serrures basé sur la valeur de l'égalité plutôt que lock-égalité, je suis venu avec la mise en œuvre suivante:Scala Sémantique partielle d'application de fonction + verrouillage avec synchronisation

/** 
    * 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) 
} 

j'ai écrit quelques tests pour vérifier que mon verrou fonctionne comme prévu:

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

Les tests ci-dessus fonctionnent comme prévu (testés sur des millions de tests). Mais, quand je change la ligne suivante:

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

à ceci:

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

les tests échouent.

attendus:

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

OU

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

Actual:

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

Les deux ci-dessus des morceaux de code doivent être les mêmes mais les essais (le lock(id) entre toujours simultanément pour le même id) pour la deuxième saveur (celle avec par tial application) de code. Pourquoi?

+0

voir aussi: http://stackoverflow.com/questions/4918362/partial-function-application-prematurely-runs-codeblock-when-use-with-underscor – pathikrit

Répondre

2

Par défaut, les paramètres de la fonction sont évalués avec empressement. Ainsi

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

est équivalent à

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

dans ce cas, f est évaluée avant le bloc synchronisé.