Antología del Disparate - I
Advertencia: Cualquier parecido con
la realidad, es una cochina casualidad...
la realidad, es una cochina casualidad...
n buen señor, presuntamente un experto en casi todo, bien pagado y considerado, recibe el encargo de crear una "base de datos en memoria"... tarea que, para el resto de nosotros, los mortales, consiste en meter objetos en un contenedor. Monta su colección basada en ICollection (estamos en los remotos tiempos de .NET v1.1) y añade internamente un par de Hashtable's para permitir el acceso directo dada una clave. Como las "cosas" que tiene que guardar en el contenedor no tienen una clave primaria clara, decide con buen tino añadir una clave artificial, que implementa de la siguiente manera:
public class Cosa
{
private static int staticId;
private int instanceId;
// ...
}
El campo estático staticId contiene el valor a asignar a la próxima "cosa" que se cree, mientras que instanceId representa la clave única y artificial de cada instancia. Esta clave, como sospechará, se asigna automáticamente durante la construcción de instancias.
El problema es que la aplicación en cuestión consume hilos a manos llenas, y es posible que se creen dos "cosas" a la misma vez. Nuestro sujeto, que no es del todo lerdo, sabe que tiene que protegerse, y diseña el siguiente constructor:
public Cosa()
{
// ¡INCORRECTO!
Interlocked.Increment(ref staticId);
instanceId = staticId;
}
¿Dónde está el disparate? ¿Cómo, y cuándo, cree usted que se manifestará? ¿Cómo se puede corregir?
El disparate, evidentemente, está en el uso incorrecto de la clase Interlocked. Se trata, en realidad, de un caso flagrante de cargocultismo. El "experto" conocía un par de datos correctos:
- El patrón de asignación de un identificador autoincremental exige el uso de primitivas de sincronización, o de lo contrario, pueden producirse anomalías.
- La clase Interlocked permite sustituir determinados usos de la instrucción lock de forma más eficiente.
Hasta ahí, todo bien: el experto había oído sonar campanas. Lo malo es que no sabía dónde. No se tomó la molestia de leerse hasta el final la ayuda de Interlocked (¿para qué?). Peor aún: no se preguntó cómo demonios se las arregla esta clase para que el código sea equivalente a un lock en toda regla. El concepto de "actualización atómica" le resultó tan extraño como escuchar a un marine hablar con Alpha Bravo Charlie a través de su mágica radio-cocotero.
Para explicar qué es lo que está mal en el código original, es mejor que le presente el código correcto:
public Cosa()
{
// ¡CORRECTO!
instanceId = Interlocked.Increment(ref staticId);
}
Como ve, el arreglo es sencillo: hay que asignar a instanceId el resultado de la llamada a Interlocked.Increment, en vez de desperdigar la llamada y la asignación entre dos instrucciones; enseguida veremos por qué. En caso de que no existiese Interlocked, la forma correcta de realizar esta operación sería:
public Cosa()
{
lock (typeof(Cosa))
instanceId = ++staticId;
}
Los más puristas verán con malos ojos el bloqueo sobre el typeof, pero lo he escrito así para abreviar. Pues bien: el código anterior es equivalente al código correcto. El motivo por el que usamos lock es evitar que, entre la modificación de la variable global y la asignación de su valor en el campo de la instancia pueda cederse el control a otro hilo. Si esto ocurriese, podría ser que recibiésemos un valor duplicado en instanceId. La clase Interlocked, precisamente, nos garantiza que el incremento de la variable estática y la copia del valor correcto como valor de retorno tengan lugar de forma atómica, sin posibilidad de que otro hilo pueda acceder a la variable estática modificada. La llamada a Increment, por otra parte, es mucho más eficiente que el uso de lock.
Por el contrario, en el código erróneo original, el "genio" ha roto la atomicidad de la operación... por no tomarse la molestia de examinar bien la documentación o meditar sobre el problema. ¿Cuándo se manifestó el error? Esta es la parte que suele infundir horror en los corazones sensibles: el error fue detectado casi por casualidad:
- Por una parte, para que se produzca el error, tienen que intercalarse las operaciones de dos hilos de manera muy especial. Estos problemas no se detectan con una simple ejecución... si no hay suerte.
- Para rematar, y esto es tan grave como todo lo anterior, la aplicación tenía un sistema de "manejo" de errores demencial, que se tragaba todas las excepciones gracias a cantidades industriales de instrucciones try/catch repartidas uniformemente a lo largo y ancho del código. En caso de producirse un error, la aplicación se lo callaba taimadamente.
Un buen día, fue necesario sacar una copia de la colección de "cosas" en un dataset vulgar y corriente... y ahí fue que saltó la violación de la unicidad.
Y usted quizás se pregunte si mi personaje imaginario (¡porque cualquier parecido con la realidad... ya sabe!) se merece que lo vapulee de este modo. Pues sí, se lo merece. En este caso, el resultado de su metedura de pata es que, en ocasiones, la aplicación veía muertos y perdía registros fantasmas. Nada grave: sólo se trataba de dinero. Imagine, en cambio, que la aplicación controlase aviones, o trenes de pasajeros, o que monitorizase constantes vitales en un hospital. ¡Todos a la cárcel! Hace mucho, migrando una aplicación de altas hospitalarias desde dBase a InterBase, tropecé con el expediente médico de un caballero al que habían, aparentemente, operado de los ovarios. En realidad, por un error parecido, la clave primaria del expediente de este señor coincidía con la clave del expediente de una mujer, y los registros de detalles eran compartidos por ambos expedientes.
Moraleja: las optimizaciones son buenas. Pero, ¡por favor!, léase bien el manual antes de experimentar con métodos que no conoce.
Vea también: Más ligero que un bloqueo.
Etiquetas: C#, CLR, disparates, hilos, sincronización
9 Comments:
Me parece que, pese a que el post pueda ser divertido y anecdótico, y por otro lado mostrar determinadas cosas, el tono de prepotencia general sobra bastante.
Bastante más constructivo sería una explicación de por qué el código está mal en lugar de la simple mofa desde un punto de vista que suena a "yo jamás cometo errores, esto jamás podía pasarme a mi"... entre otras cosas porque sería mucho más educativo si señalaras en el post, al menos, porque separar el interlocked increment de la asignación supone un problema (por la entrada de otro hilo justo entre ambas instrucciones)
el tono de prepotencia general sobra bastante.
¿Y no es también esta observación una manifestación de prepotencia? Jorge, esta página es gratuita y no me reporta un duro. Por lo tanto, me reservo el derecho de decidir lo que sobra o falta.
Y con la "constructividad" me construyo un sombrero de papel para protegerme de la lluvia... uh, que penita, ya se rompió...
"yo jamás cometo errores, esto jamás podía pasarme a mi"
Hombre, puedo cometer errores... pero errores tan estúpidos como éste no sobrevivirían en mi empresa más de una semana.
Y ya me gustaría verte lidiando con los hideputas en cuestión. A ver si ibas a usar la "crítica constructiva", si ibas a hacer de hermanita de la caridad, o si te ibas a cabrear e incendiarles el chiringuito por cabrones. Al menos mi incendio es verbal.
Saludos Ian.
hay muchos cargocultistas o un hibrido entre pollos de skiner y estos que se sienten ofendidos cuando expresas algo que le afecta a ellos.
Lo que realmente me llama la atención y me hace entender mejor las cosas es como el autor de este blogs y escritor de libros se expresa. No me gusta el aburrimiento y la forma de explicar como no debe hacerse y como se hace de la manera que funciona bien es algo de agradecer.
En internet no se ven muchos blogs que se dediquen a explicar estos temas y los que lo hacen se quedan siempre muy cortos además de ponerse a redundar sobre cosas que ya se conocen hasta donde yo he visto.
A mi me preocupa esto:
recibe el encargo de crear una "base de datos en memoria"... tarea que, para el resto de nosotros, los mortales, consiste en meter objetos en un contenedor.
Pues la única diferencia entre una base de datos en memoria y una en disco es que con la primera no se utiliza un disco duro como dispositivo de almacenamiento (aunque a veces sí para el log). El resto tiene que ser igual: tablas, vistas, consultas, transacciones, restricciones de integridad, triggers, etc.
Existen bastantes productos que hacen esto.
Hola, Junior. Además, el mensaje que intento comunicar es muy importante: nuestras decisiones tienen consecuencias. Es una suerte que mi ejemplo (imaginario, por supuesto) no sea una aplicación para un hospital o para aplicaciones industriales, o para transportes. Si algo falla, lo "peor" que puede pasar es que alguien pierda dinero... que ya es bastante malo.
la única diferencia entre una base de datos
Por eso lo entrecomillé: para quien tiene cabeza de martillo, todos son clavos. "Base de datos en memoria" es la forma que tienen los escarabajos peloteros de referirse a cualquier estructura de datos en memoria. Por supuesto, no es una "base de datos" lo que montaron, al fin y al cabo.
Ah, vale. Había entendido otra cosa.
Hombre, base de datos también es, pero muy cutre. Trabajar así con una base de datos compleja es una locura.
Y con respecto a lo que dice Jorge yo no veo ningún tono prepotente. Una cagada es una cagada, y además está suficientemente explicado por que está mal.
Una cagada es una cagada
... y si además, conocieras a los personajes imaginarios (porque son imaginarios, por supuesto) de esta historia :)
Publicar un comentario
<< Home