Tengo una variable global llamada my_list que contiene una lista vinculada (es decir, la variable es un puntero al primer miembro de la lista). Esta variable solo puede ser editada por dos subprocesos, el subproceso A y el subproceso B. En caso de que la lista quede vacía, la variable se establece en NULL . Cuando esto sucede, la variable se establece en NULL primero y luego se libera la memoria.
Como es editado por dos subprocesos, uso un mutex cada vez que el subproceso A o el subproceso B tocan la variable my_list . Nada inusual hasta ahora.
Pero luego viene un tercer hilo, el hilo C. Este hilo nunca tocará la lista enlazada de ninguna manera, pero necesitará saber de vez en cuando si la lista está vacía o no. Entonces, lo único que hará el subproceso C es
if (my_list) { do_something_completely_unrelated(); }¿Tengo que usar un mutex para eso? Creo que es una operación atómica, por lo que no se requiere mutex. ¿Es esto correcto?
EDITAR
Agregaré aquí un poco de contexto. La razón por la que me gustaría evitar el uso de un mutex es que la lista se actualiza rara vez (siempre), pero la verificación que proviene del subproceso C ocurre cada pocos milisegundos, por lo que cuantas menos operaciones, mejor.
Si la lista parece no ser NULL , entonces el subproceso C activa una verificación adecuada usando un mutex , y si se confirma que la lista no está vacía, el subproceso C detiene su verificación obsesiva.
Según el estándar, es un comportamiento indefinido leer una variable cuando está escrita si al menos una de las operaciones no es atómica.
Desde la sección de comentarios, parece preguntarse si bloqueará su programa o si lo peor que puede pasar es que obtenga el valor incorrecto. Tu comentario, énfasis mío:
Gracias. Pero concretamente ¿qué podría pasar mal? Digamos que la variable es
NULLpero el subproceso A la está cambiando en este momento para que se dirija axxxxxx. El subproceso C intenta leerlo. ¿Qué valor puede obtener? ONULLoxxxxxx, y ambos están bien. ¿Me equivoco al suponer que el programa no fallará?
Diría que esta simple verificación probablemente no hará que su programa se bloquee. La operación de comprobación es segura en el sentido de que lo más probable es que obtenga un valor. El valor puede ser incorrecto, pero lo más probable es que la verificación por sí sola no bloquee su programa.
Sin embargo, ES un comportamiento indefinido:
La ejecución de un programa contiene una carrera de datos si contiene dos acciones en conflicto en diferentes hilos, al menos uno de los cuales no es atómico, y ninguno sucede antes que el otro. Cualquier carrera de datos de este tipo da como resultado un comportamiento indefinido.
Norma C18, apartado 5.1.2.4, párrafo 35
Luego publicaste este comentario de seguimiento:
En realidad, esto no es tan importante, pero ¿estoy en lo cierto al suponer que si el subproceso A está cambiando el valor de
NULLaxxxxxx(o viceversa) definitivamente no obtendréyyyyyy, sino NULL oxxxxxx?
Aquí diría que su suposición es incorrecta. AFIK, no hay nada en el estándar que diga que la asignación de un puntero debe ser una operación atómica, y me sorprendería si lo fuera. Y como mencioné anteriormente, ES un comportamiento indefinido.
_Atomic Declare la lista con el calificador _Atomic . Tiene algunas limitaciones. Por ejemplo:
No es una función obligatoria, por lo que podría reducir la portabilidad
No se puede usar con arreglos.
Si se usa con una estructura, los campos de la estructura no se pueden acceder individualmente
2 y 3 no deberían importarle, ya que la lista es solo un indicador.
Lea sobre _Atomic aquí: https://en.cppreference.com/w/c/language/atomic
Si _Atomic no es una opción, aquí hay una solución en pseudocódigo:
if ( now() - lastUpdated > ms ) // If it was more than ms milliseconds since last update lock(myMutex) myListCopy = myList unlock(myMutex) lastUpdated = now() if(myListCopy) do_something_completely_unrelated(); lastUpdated y myListCopy son preferiblemente variables que son locales a la función, pero la parte importante es que el subproceso A y B nunca las toca.