Recientemente, hice una pregunta, con el título "¿Es seguro el hilo de malloc?" , y dentro de eso pregunté, "¿Malloc es reentrante?"
Tenía la impresión de que todos los reentrantes son seguros para subprocesos.
¿Es incorrecta esta suposición?
Recientemente, hice una pregunta, con el título "¿Es seguro el hilo de malloc?" , y dentro de eso pregunté, "¿Malloc es reentrante?"
Tenía la impresión de que todos los reentrantes son seguros para subprocesos.
¿Es incorrecta esta suposición?
Respuestas:
Las funciones reentrantes no dependen de las variables globales que están expuestas en los encabezados de la biblioteca de C ... tome strtok () vs strtok_r () por ejemplo en C.
Algunas funciones necesitan un lugar para almacenar un 'trabajo en progreso', las funciones reentrantes le permiten especificar este puntero dentro del propio almacenamiento del hilo, no en un global. Dado que este almacenamiento es exclusivo de la función de llamada, se puede interrumpir y volver a ingresar (reentrante) y dado que en la mayoría de los casos no se requiere la exclusión mutua más allá de lo que implementa la función para que esto funcione, a menudo se considera que son hilo seguro . Sin embargo, esto no está garantizado por definición.
errno, sin embargo, es un caso ligeramente diferente en los sistemas POSIX (y tiende a ser el bicho raro en cualquier explicación de cómo funciona todo esto) :)
En resumen, reentrante a menudo significa seguro para subprocesos (como en "use la versión reentrante de esa función si está usando subprocesos"), pero seguro para subprocesos no siempre significa reentrante (o al revés). Cuando busca seguridad en subprocesos, la concurrencia es lo que debe pensar. Si tiene que proporcionar un medio de bloqueo y exclusión mutua para usar una función, entonces la función no es inherentemente segura para subprocesos.
Pero no es necesario examinar todas las funciones. malloc()
no tiene necesidad de ser reentrante, no depende de nada fuera del alcance del punto de entrada para un hilo dado (y es en sí mismo seguro para hilos).
Las funciones que devuelven valores asignados estáticamente no son seguras para subprocesos sin el uso de un mutex, futex u otro mecanismo de bloqueo atómico. Sin embargo, no necesitan volver a entrar si no van a ser interrumpidos.
es decir:
static char *foo(unsigned int flags)
{
static char ret[2] = { 0 };
if (flags & FOO_BAR)
ret[0] = 'c';
else if (flags & BAR_FOO)
ret[0] = 'd';
else
ret[0] = 'e';
ret[1] = 'A';
return ret;
}
Entonces, como puede ver, tener múltiples subprocesos usando eso sin algún tipo de bloqueo sería un desastre ... pero no tiene ningún propósito volver a entrar. Te encontrarás con eso cuando la memoria asignada dinámicamente sea tabú en alguna plataforma integrada.
En la programación puramente funcional, reentrante a menudo no implica que sea seguro para subprocesos, dependería del comportamiento de funciones definidas o anónimas pasadas al punto de entrada de la función, recursividad, etc.
Una mejor manera de poner 'seguro para subprocesos' es segura para el acceso concurrente , lo que ilustra mejor la necesidad.
TL; DR: una función puede ser reentrante, segura para subprocesos, ambas o ninguna.
Vale la pena leer los artículos de Wikipedia sobre seguridad de subprocesos y reentrada . Aquí hay algunas citas:
Una función es segura para subprocesos si:
solo manipula las estructuras de datos compartidas de una manera que garantiza la ejecución segura de varios subprocesos al mismo tiempo.
Una función es reentrante si:
se puede interrumpir en cualquier momento durante su ejecución y luego volver a llamar de forma segura ("reingresar") antes de que sus invocaciones anteriores completen la ejecución.
Como ejemplos de posible reentrada, Wikipedia da el ejemplo de una función diseñada para ser llamada por interrupciones del sistema: supongamos que ya se está ejecutando cuando ocurre otra interrupción. Pero no crea que está seguro solo porque no codifica con interrupciones del sistema: puede tener problemas de reentrada en un programa de un solo subproceso si usa devoluciones de llamada o funciones recursivas.
La clave para evitar confusiones es que reentrante se refiere a la ejecución de un solo hilo. Es un concepto de la época en que no existían sistemas operativos multitarea.
Ejemplos
(Ligeramente modificado de los artículos de Wikipedia)
Ejemplo 1: no seguro para subprocesos, no reentrante
/* As this function uses a non-const global variable without
any precaution, it is neither reentrant nor thread-safe. */
int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Ejemplo 2: seguro para subprocesos, no reentrante
/* We use a thread local variable: the function is now
thread-safe but still not reentrant (within the
same thread). */
__thread int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Example 3: not thread-safe, reentrant
/* We save the global state in a local variable and we restore
it at the end of the function. The function is now reentrant
but it is not thread safe. */
int t;
void swap(int *x, int *y)
{
int s;
s = t;
t = *x;
*x = *y;
*y = t;
t = s;
}
Example 4: thread-safe, reentrant
/* We use a local variable: the function is now
thread-safe and reentrant, we have ascended to
higher plane of existence. */
void swap(int *x, int *y)
{
int t;
t = *x;
*x = *y;
*y = t;
}
t = *x
, calls swap()
, then t
will be overridden, leading to unexpected results.
swap(5, 6)
being interrupted by a swap(1, 2)
. After t=*x
, s=t_original
and t=5
. Now, after the interruption, s=5
and t=1
. However, before the second swap
returns it will restore context, making t=s=5
. Now, we go back to the first swap
with t=5 and s=t_original
and continue after t=*x
. So, the function does appear to be re-entrant. Remember that every call gets its own copy of s
allocated on stack.
It depends on the definition. For example Qt uses the following:
A thread-safe* function can be called simultaneously from multiple threads, even when the invocations use shared data, because all references to the shared data are serialized.
A reentrant function can also be called simultaneously from multiple threads, but only if each invocation uses its own data.
Hence, a thread-safe function is always reentrant, but a reentrant function is not always thread-safe.
By extension, a class is said to be reentrant if its member functions can be called safely from multiple threads, as long as each thread uses a different instance of the class. The class is thread-safe if its member functions can be called safely from multiple threads, even if all the threads use the same instance of the class.
but they also caution:
Note: Terminology in the multithreading domain isn't entirely standardized. POSIX uses definitions of reentrant and thread-safe that are somewhat different for its C APIs. When using other object-oriented C++ class libraries with Qt, be sure the definitions are understood.