2011-07-29 11 views
19

me preguntaba si existe alguna forma "fácil" de actualizar las colecciones de scala inmutables de forma segura. Considere el siguiente código:Actualizando las colecciones de Scala de forma segura

class a { 
    private var x = Map[Int,Int]() 

    def update(p:(Int,Int)) { x = x + (p) } 
} 

Este código no es seguro para subprocesos, ¿correcto? Con esto quiero decir que si tenemos dos hilos invocando el método de actualización y digamos que x es un mapa que contiene {1 => 2} y el hilo A invoca la actualización ((3,4)) y solo logra ejecutar el x + (p) parte del código. Luego ocurre la reprogramación y el hilo B invoca la actualización ((13, 37)) y actualiza con éxito la variable x. El hilo A continúa y termina.

Después de todo esto, el valor x equivaldría a un mapa que contenga {1 => 2, 3 => 4}, ¿correcto? En lugar de deseado {1 => 2, 3 => 4, 13 => 37}. ¿Hay una manera simple de arreglar eso? Espero que no se pueda entender lo que estoy preguntando :)

Btw, sé que hay soluciones como Akka STM, pero preferiría no utilizarlas, a menos que sea necesario.

¡Muchas gracias por cualquier respuesta!

editar: Además, preferiría una solución sin bloqueo. Eeeew :)

+1

uso Akka STM http://akka.io/docs/akka/1.1.3/ scala/stm.html – Phil

+0

+! para Eeeew! : D – Daniel

Respuesta

18

En su caso, como escribió Maurício, su colección ya es segura porque es inmutable. El único problema es reasignar el var, que puede no ser una operación atómica. Para este problema en particular, la opción más fácil es usar las clases agradables en java.util.concurrent.atomic, es decir, AtomicReference.

import java.util.concurrent.atomic.AtomicReference 

class a { 
    private val x = new AtomicReference(Map[Int,Int]()) 

    def update(p:(Int,Int)) { 
    while (true) { 
     val oldMap = x.get // get old value 
     val newMap = oldMap + p // update 
     if (x.compareAndSet(oldMap, newMap)) 
     return // exit if update was successful, else repeat 
    } 
    } 
} 
+1

parece que es más simple sincronizar todo este bloque. Quiero decir, en definitiva, las operaciones con overlock crean una barrera de memoria ... – tuxSlayer

+1

Puede ser más simple sincronizar, pero esto es seguro sin necesidad de sincronización y podría decirse que es más eficiente, excepto si el mapa se actualiza con mucha frecuencia por muchos hilos. –

+1

En realidad, requiere menos recursos usar var y sincronizar en la operación de intercambio en caliente. Vea esto: [link] (http://twitter.github.io/scala_school/concurrency.html) - el "¿Esto cuesta algo?" sección –

3

La colección en sí es segura para subprocesos ya que no tiene un estado mutable compartido, pero su código no es y no hay forma de arreglar esto sin bloqueo, ya que tiene un estado mutable compartido. Su mejor opción es bloquear el método mismo marcándolo como sincronizado.

La otra solución sería usar un mapa simultáneo mutable, posiblemente java.util.concurrent.ConcurrentMap.

0

Re. La respuesta de Jean-Philippe Pellet: usted puede hacer esto un poco más reutilizable:

def compareAndSetSync[T](ref: AtomicReference[T])(logic: (T => T)) { 
    while(true) { 
    val snapshot = ref.get 
    val update = logic(snapshot) 
    if (ref.compareAndSet(snapshot, update)) return 
    } 
} 

def compareSync[T,V](ref: AtomicReference[T])(logic: (T => V)): V = { 
    var continue = true 
    var snapshot = ref.get 
    var result = logic(snapshot) 
    while (snapshot != ref.get) { 
    snapshot = ref.get 
    result = logic(snapshot) 
    } 
    result 
} 
+0

Del mismo modo, http://blog.scala4java.com/2012/03/atomic-update-of-atomicreference.html Parece un caso de uso bastante común. – experquisite

Cuestiones relacionadas