¿Estructuras de datos de árbol concurrentes de tiempo de actualización constante y sin bloqueo?
20
Últimamente he estado leyendo un poco de literatura y he encontrado algunas estructuras de datos bastante interesantes.
He investigado varios métodos diferentes para reducir el tiempo de actualización a peor tiempo de actualización [1-7].O ( 1 )
Recientemente comencé a buscar estructuras de datos sin bloqueo para admitir el acceso concurrente eficiente.
¿Se ha utilizado alguna de estas técnicas de tiempo de actualización en el peor de los casos en la implementación de estructuras de datos sin bloqueo?O ( 1 )
Pregunto porque; para mí, parecen la extensión práctica obvia de esta "mejora teórica".
Utilicé la biblioteca práctica de estructuras de datos sin bloqueo antes. Tienen cierto soporte de estructuras de datos de árbol sin bloqueo. Quizás tengas lo que estás buscando.
Reza
Respuestas:
4
O ( 1 ) no ayuda en sí mismo. En una estructura de datos sin bloqueo, debe haber una única instancia atómica cuando su estructura de datos parece cambiar. Todos los invariantes de representación deben estar vigentes tanto inmediatamente antes como inmediatamente después de ese instante atómico.
Esto significa que si está realizando una modificación en la estructura de datos, la característica importante es que puede hacer todas las modificaciones en una estructura de datos privada y luego intercambiar las modificaciones en una sola instrucción atómica.
La libertad de bloqueo suele ser más fácil cuando las estructuras de datos son inmutables ( puramente funcionales ). Simplemente mantenga un puntero global a la versión actual de la estructura de datos. Los lectores no necesitan bloquear nada. Las modificaciones a la estructura de datos se efectúan intercambiando el puntero global a una estructura de datos inmutable a otra.
Por ejemplo: si tiene un árbol equilibrado de árbol puramente funcional, usted:
Registre el puntero global actual a la raíz del árbol.
Cree un nuevo árbol que inserte o elimine un nodo. (Esto es logarítmico en tiempo y espacio en la cantidad de nodos actualmente en el árbol, e implica crear nuevos nodos desde el punto de modificación hasta la raíz, y solo señalar todo lo nuevo en las partes antiguas de la versión anterior de la estructura de datos. )
Compare atómicamente e intercambie el puntero global a la raíz. (Tenga en cuenta que esto podría fallar si ha ocurrido otra modificación entre el momento en que registró el puntero raíz anterior y ahora. Si esto sucede, vuelva al paso 1 e intente nuevamente. Esto se denomina "control de concurrencia optimista").
Tenga en cuenta que la parte más importante es lo que dije anteriormente sobre la necesidad de mantener invariantes de representación. Por lo general, no es suficiente tener un algoritmo que atómicamente haga un cambio en el medio del árbol. ¿Por qué? Por ejemplo: puede tener un hilo lector que está en proceso de realizar un recorrido previo al pedido del árbol. Si modifica un nodo que es un antecesor del nodo que están leyendo actualmente, invalidará las condiciones previas que pensaron que habían aplicado. El lector debe poder trabajar con la estructura de datos exactamente como estaba antes de realizar el cambio, o exactamente como será después de que haya realizado el cambio. No hay algo en el medio.
Editar : como señaló @Raphael, existen técnicas para hacer que las estructuras de datos mutables estén libres de bloqueo. Una prueba por construcción de que esto siempre se puede hacer es: siempre que tenga un puntero global único a la "parte superior" de su estructura de datos, incluso si es mutable, siempre puede copiar toda la estructura de datos, realice sus modificaciones en el copie y luego, utilizando un control de concurrencia optimista, intente comparar e intercambiar el puntero a su estructura de datos recién creada para obtener el original. La belleza de las estructuras de datos funcionales basadas en árboles es que mantienen el costo de copiar en de una estructura de datos de tamaño .O ( N )O ( l o g( N) )O ( N)
Creo que las técnicas de espera activa, por ejemplo, con comparar e intercambiar, generalmente se denominan "sin bloqueo", por lo que hay algunas salidas, incluso en la configuración mutable.
Raphael
No estoy familiarizado con el término espera activa (y Google no está ayudando). (Si está hablando del trabajo de Kogan y Petrank, eso muestra cómo convertir los algoritmos sin bloqueo en sin espera). Agregué una edición sobre cómo puede lidiar con la mutabilidad para la libertad de bloqueo en general.
Wandering Logic
Por "espera activa" me refiero a algo como while ( !P ) { noOp(); } doWork();dónde noOppuede ser a sleepo similar.
Raphael
En la parte Editar , mencionó la técnica para hacer que las estructuras de datos mutables estén libres de bloqueo. Como se indicó, copiamos toda la estructura de datos, hacemos modificaciones a la copia y luego usamos la primitiva CAS. Sin embargo, ¿cómo hacer que el Copypaso sea atómico? Parece ser otro problema difícil de atomic snapshot.
hengxin
@hengxin: piense en la primitiva CAS como un operador de "publicación". Antes de publicar la estructura de datos, solo el hilo que realiza las modificaciones tiene acceso a ella. Una vez que se publica la estructura de datos, es inmutable. El paso de copia no necesita ser atómico porque lo único que un hilo podría estar copiando es una versión publicada, que es inmutable. Si dos hilos intentan mutar simultáneamente, ambos copian la misma estructura de datos inmutable, modifican sus copias locales, y luego una de las operaciones de CAS tiene éxito y la otra falla. El que falla debe volver a copiarse y actualizarse.
Respuestas:
Esto significa que si está realizando una modificación en la estructura de datos, la característica importante es que puede hacer todas las modificaciones en una estructura de datos privada y luego intercambiar las modificaciones en una sola instrucción atómica.
La libertad de bloqueo suele ser más fácil cuando las estructuras de datos son inmutables ( puramente funcionales ). Simplemente mantenga un puntero global a la versión actual de la estructura de datos. Los lectores no necesitan bloquear nada. Las modificaciones a la estructura de datos se efectúan intercambiando el puntero global a una estructura de datos inmutable a otra.
Por ejemplo: si tiene un árbol equilibrado de árbol puramente funcional, usted:
Tenga en cuenta que la parte más importante es lo que dije anteriormente sobre la necesidad de mantener invariantes de representación. Por lo general, no es suficiente tener un algoritmo que atómicamente haga un cambio en el medio del árbol. ¿Por qué? Por ejemplo: puede tener un hilo lector que está en proceso de realizar un recorrido previo al pedido del árbol. Si modifica un nodo que es un antecesor del nodo que están leyendo actualmente, invalidará las condiciones previas que pensaron que habían aplicado. El lector debe poder trabajar con la estructura de datos exactamente como estaba antes de realizar el cambio, o exactamente como será después de que haya realizado el cambio. No hay algo en el medio.
Editar : como señaló @Raphael, existen técnicas para hacer que las estructuras de datos mutables estén libres de bloqueo. Una prueba por construcción de que esto siempre se puede hacer es: siempre que tenga un puntero global único a la "parte superior" de su estructura de datos, incluso si es mutable, siempre puede copiar toda la estructura de datos, realice sus modificaciones en el copie y luego, utilizando un control de concurrencia optimista, intente comparar e intercambiar el puntero a su estructura de datos recién creada para obtener el original. La belleza de las estructuras de datos funcionales basadas en árboles es que mantienen el costo de copiar en de una estructura de datos de tamaño .O ( N )O ( l o g( N) ) O ( N)
fuente
while ( !P ) { noOp(); } doWork();
dóndenoOp
puede ser asleep
o similar.Copy
paso sea atómico? Parece ser otro problema difícil deatomic snapshot
.