2012-04-11 9 views
39

Recientemente he hecho una serie de preguntas con respecto a TVar, y todavía me preocupan los bloqueos en directo.Concurrente estructura de datos genéricos sin interbloqueos o falta de recursos

Así que pensé en esta estructura:

  1. Cada transacción obtiene una prioridad única (tal vez asignado el fin de la creación).
  2. Las transacciones intentan obtener bloqueos de lectura/escritura en los datos a los que acceden. Naturalmente, las lecturas simultáneas están bien, pero un bloqueo de escritura excluye todos los demás (tanto de lectura como de escritura).
  3. Digamos que la transacción A tiene mayor prioridad que la transacción B. Si A retiene el bloqueo, B espera, pero si B contiene el bloqueo y A lo quiere, B se inicia desde el bloqueo, A lo obtiene y la transacción B se reinicia (como con TVar). B sin embargo mantiene su prioridad actual para el reintento.
  4. Cuando se libera un bloqueo y hay transacciones esperando, pasa a la transacción de mayor prioridad, y el resto continúa esperando.

Creo que este sistema evita los interbloqueos, pero también previene la inanición (a diferencia de TVar). Me preguntaba si alguien ha implementado un sistema así, ya que parece bastante obvio y no quiero reinventar la rueda.

Por supuesto, este enfoque podría ampliarse fácilmente para permitir al usuario especificar las prioridades.

La prioridad podría ser el par (user_supplied_prio, auto_increment), con user_supplied_prio teniendo prioridad, pero las tareas de igual prioridad se resuelven en orden FIFO.

Comentario/Solución:

En realidad, cuando pienso en ello, lo que describo ya existe en Haskell, simplemente mediante el uso de un IORef envuelto alrededor de todos los datos, y sólo utilizando atomicModifyIORef. El atomicModifyIORef asegurará que las transacciones se apliquen en secuencia. Sin embargo, uno puede pensar que esto significa que la estructura de datos es secuencial (es decir, está efectivamente limitada a un hilo) pero en realidad es paralela debido a la pereza.

Para explicar esto, considere una costosa función f. Vamos a aplicar esto a un Data.Map a los datos con la clave "foo".

Esto reemplaza (foo -> x) con (foo -> future(f x)). Este hilo continuará resolviendo lo que realmente es (f x), pero mientras tanto podemos aplicar g a "bar". Como aplicar g a "bar" no necesita el resultado de "foo", podemos resolver esto al mismo tiempo.

Sin interbloqueos, sin inanición, eventualmente todas las transacciones serán procesadas (más o menos en el orden en que se reciben).

+2

No tengo conocimiento de que exista tal sistema en Haskell. Parece demasiado complejo para la mayoría de los usuarios, y bastante complicado de implementar. En particular, la invalidación de un bloqueo asignado requeriría un sondeo (algo tedioso de codificar) o excepciones de asincronización (un conjunto de otras lombrices). Sugeriría que solo pruebes STM para tu implementación y veas cómo funciona; Los algoritmos STM son generalmente lo suficientemente simples como para que no sea una inversión de tiempo significativa si necesita reemplazarlos. –

+3

Hablando desde el punto de vista de STM, agregar un mecanismo de prioridad al tiempo de ejecución (similar al que mencionaste basado en la fecha) resolverá el problema de bloqueo en vivo, creo. Sin embargo, podría limitar seriamente las opciones del programador, lo que incluso podría ser la razón por la cual no existe tal mecanismo en el tiempo de ejecución actual de STM. PD: es posible que desee marcar algunas de las respuestas a sus preguntas anteriores como "respuesta aceptada", para que la comunidad sepa si la pregunta se ha resuelto. – Peter

+1

¿Cuáles son los requisitos de rendimiento? Puede obtener lo que quiere a través de medios de grano suficiente, como un bloqueo de ticket global (http://en.wikipedia.org/wiki/Ticket_lock), o al poner en cola todas las acciones que se ejecutarán en serie mediante un solo hilo. Los métodos de sincronización sofisticados tienden a tener una sobrecarga más alta.Si la sincronización global no es un cuello de botella para una determinada carga de trabajo, probablemente sea más rápida. – Heatsink

Respuesta

1

Puede configurar un hilo de trabajo para procesar todas las solicitudes de forma determinista, para que nadie se muera de hambre. Esta estrategia sería razonablemente eficiente e inmune a Livelock.

-- yes, this is a horrible name 
createManagerFactory :: a -> IO ((IO a), IO (((a -> a) -> IO a))) 

IO a es una acción que consulta de forma segura y rápida el valor con una acción STM de solo lectura. (a -> a) es una función pura que modifica el valor, entonces ((a -> a) -> IO a) es una acción que toma una función modificadora, modifica el valor de manera segura y devuelve el nuevo valor.

Ejecute esto una vez para inicializar la fábrica.

(query, modifierFactory) <- createManagerVactory initValue 

Luego, para cada subproceso, ejecute esto para generar un nuevo modificador.

myModify <- modifierFactory 

createManagerFactory haría lo siguiente:

  • Crear una TVAR contiene initValue (llámese valueTVar).
  • Crear una TVAR que contiene una colección vacía de TVAR (ya sea un (a -> a)) (llamarlo el modifyTVarCollection)
  • retorno (atómicamente $ readTVar valueTVar) como la 'consulta' resultado
  • cambio una modifierFactory que sabe sobre el modifierFactory modifyTVarCollection

haría esto:

  • Crear un nuevo TVAR (ya sea un (a -> a)) (llámese modifyTVar), inicializar a un (izquierda una) con el valor actual del valorTVar y agréguelo a modifyTVarCollection
  • devuelve una acción modificadora que carga (Derecha (a -> a)) en el modifyTVar en una acción STM, luego en otra acción STM reintenta hasta que modifyTVar contenga a (resultado de la izquierda a), luego devuelva ese valor.

El subproceso de trabajo iría en este bucle:

  • en una sola acción, agarrar todos los TVars de consulta de la modifyTVarCollection, y comprobar sus valores. Si todos contienen valores (de la izquierda a), vuelva a intentarlo (esto bloquearía hasta que otro subproceso cargue su modifyTVar con una función modificadora, o modifierFactory creó un nuevo modificadorTVar y lo agregó a la colección). Devuelve una lista de todos los TV modificados que contienen una derecha (a -> a)
  • Iteramente sobre todos los modifiedTVars devueltos. Para cada TVar, realice una acción que lea la función modificadora, realice la modificación de forma segura y devuelva el resultado al modifyTVar como (a la izquierda)
Cuestiones relacionadas