Pregunta de entrevista sobre manejo de concurrencia en Node JS.
Asistí a una entrevista de codificación de NodeJS. Obtuve el siguiente código que se ejecuta de forma asíncrona desde diferentes navegadores (se supone). Nuestra solución necesita bloquear la ejecución de la función si la actualización por ID es la misma pero se llama desde un lugar diferente (digamos, desde un navegador). Y luego liberar el bloqueo para la ejecución de la siguiente solicitud.
Aquí no se deben realizar cambios en el código mencionado a continuación.
async function update(id, data) {
console.log(`start -> id:${id}, data:${data}`);
await randomDelay(); // La actualización está sucediendo aquí
console.log(`end -> id:${id}, data:${data}`);
}
//=============================================================================
//================= No cambiar nada a continuación ===============================
//=============================================================================
//---- update() se está llamando desde muchos lugares ----
update(1, "navegador 1");
update(1, "navegador 2");
//========================= Funciones de utilidad ===================================
//========================= No cambies nada aquí================================
async function sleep(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), ms);
});
}
async function randomDelay() {
const randomTime = Math.round(Math.random() * 1000);
return sleep(randomTime);
}
Esto dará una salida como la siguiente.
start -> id:1, data:navegador 1
start -> id:1, data:navegador 2
end -> id:1, data:navegador 1
end -> id:1, data:navegador 2
La respuesta esperada es:
start -> id:1, data:navegador 1
end -> id:1, data:navegador 1
start -> id:1, data:navegador 2
end -> id:1, data:navegador 2
Ten en cuenta los comentarios en el código “No cambies nada a continuación”. ¿Cuál sería la solución posible?
davy.ai
Para resolver este problema, necesitamos implementar un mecanismo de bloqueo para evitar la ejecución concurrente de la función
update
con el mismo parámetroid
. Una forma de hacer esto es utilizando un hashmap y un cerrojo (mutex lock).Podemos crear un objeto hashmap accesible globalmente para almacenar el estado de los ids bloqueados. Cada vez que se llama a la función
update
, verificamos si el id ya está bloqueado. Si lo está, esperamos hasta que se libere el bloqueo antes de ejecutar la actualización. Si no lo está, adquirimos el bloqueo y procedemos con la actualización. Después de completar la actualización, liberamos el bloqueo para que las actualizaciones posteriores puedan ejecutarse.Aquí está el código modificado:
En este código modificado, primero definimos un objeto global
locks
para almacenar el estado de los ids bloqueados. La funciónupdate
verifica si el id está bloqueado verificando si existe el valorlocks[id]
. Si existe, esperamos a que se libere el bloqueo usandoawait locks[id]
. Una vez que se libera el bloqueo, procedemos a ejecutar la actualización. Antes de ejecutar la actualización, adquirimos el bloqueo para elid
dado creando una nuevaPromise
y estableciéndola como el valor delocks[id]
. Después de ejecutar la actualización, liberamos el bloqueo eliminando la entradalocks[id]
y resolviendo la promesa del bloqueo conlock.then(() => {})
.De esta manera, nos aseguramos de que las actualizaciones con el mismo parámetro
id
no se ejecuten de manera concurrente, sino que se ejecuten una a la vez en el orden en que se recibieron.