2010-10-05 15 views
5

Mientras editaba una clase con una larga historia, me obsesionó el hábito particular del arquitecto de ajustar su secuencia va_start -> va_end en un mutex. El registro de cambios para esa adición (que se realizó hace unos 15 años y no se revisó desde entonces) señaló que era porque va_start et. todo no fue reentrante.¿Se reentrató va_start (etc.)?

No estaba al tanto de tales problemas con va_start, ya que siempre pensé que era solo una macro para algunas matemáticas de punteros de pila. ¿Hay algo aquí que no conozco? No deseo cambiar este código si habrá efectos secundarios.

En concreto, la función en cuestión se parece mucho a esto:

void write(const char *format, ...) 
{ 
    mutex.Lock(); 
    va_list args; 
    va_start(args, format); 
    _write(format, args); 
    va_end(args); 
    mutex.Unlock(); 
} 

esto se le llama desde varios subprocesos.

+0

Si el comando _write bloquea el IO serie en un nivel de byte o en un nivel de búfer, puede que desee tener un bloqueo de nivel más alto para hacer que la llamada de write() sea más atómica. No hay nada más molesto con tener 2 hilos llamados 'printf (" foo ")' y 'printf (" bar ")' y obtener '" fboaor "' en tu salida, en lugar de '" foobar "' o '" barfoo "' . –

Respuesta

5

En cuanto a la reentrada en serie (es decir., si foo() usa va_start es seguro para foo() para llamar al bar() que también usa va_start), la respuesta es que está bien, siempre y cuando la instancia va_list no sea la misma. El estándar dice:

Ni la macro va_start ni va_copy se invocarán para reinicializar ap sin una invocación interviniente de la macro va_end para el mismo ap.

Por lo tanto, estás bien, siempre y cuando se utiliza una diferente va_list (denominado anteriormente ap).

Si por reentrada quieres decir hilo seguro (que supongo que eres, ya que mutexes están involucrados), tendrás que mirar a la implementación de los detalles. Dado que el estándar C no habla de multi-threading, este problema depende realmente de la implementación para asegurarlo. Me imagino que puede ser difícil hacer va_start thread-safe en algunas arquitecturas extrañas o pequeñas, pero creo que si trabajas en una plataforma convencional moderna es probable que no tengas problemas.

En las plataformas más convencionales, siempre que un argumento diferente va_list se está pasando a la va_start macro que no debería tener problemas con múltiples hilos que pasan a través de la 'misma' va_start. Y dado que el argumento va_list normalmente está en la pila (y, por lo tanto, diferentes hilos tendrán diferentes instancias) generalmente se trata de diferentes instancias del va_list.

Creo que en su ejemplo, los mutexes son innecesarios para el uso de varargs. Sin embargo, si es write(), ciertamente tendría sentido que se serializara una llamada write() para que no haya múltiples hilos write() atornillando la salida de cada uno.

+0

Acerca de las llamadas serializadas: Sí, un mutex pertenece aquí, solo quería ponerlo en un nivel inferior. Originalmente estaba en un nivel inferior, pero este compromiso hace 15 años lo movió a donde lo ves, afirmando que el mutex también necesitaba proteger a la lista va_, ¡lo que no creía! – Nate

2

Bueno, la forma en que el acceso de argumentos variable se implementa en C hace que sea bastante obvio que los objetos va_list almacena algunos estado interno. Eso hace que no vuelva a entrar, lo que significa que llamar al va_start en un objeto va_list invalidaría el efecto del va_start anterior. Pero aún más precisamente, C explícitamente prohíbe invocando va_start nuevamente en un objeto va_list antes de "cerrar" la sesión va_start invocada anteriormente con va_end.

Se supone que un objeto va_list se utiliza de forma "no superpuesta": va_start...va_end. Después de eso, puede hacer otro va_start en el mismo objeto va_list. Pero tratando de solapar las sesiones va_start...va_end en el mismo objeto va_list no funcionarán.

P.S. En realidad, en teoría, es posible, por supuesto, implementar algún estado interno basado en LIFO en cualquier iterador basado en sesión. Es decir. es teóricamente posible permitir sesiones anidadas va_start...va_end en el mismo objeto va_list (lo que hace que vuelva a entrar en ese sentido). Pero la especificación de la biblioteca C no proporciona nada de eso.

Tenga en cuenta que en C99 va_list los objetos se pueden copiar por va_copy. Por lo tanto, si necesita explorar la misma lista de argumentos mediante varias sesiones va_start...va_end superpuestas, siempre puede lograr eso creando varias copias independientes del va_list original.

P.P.S. Observando la muestra de código que proporcionó ... En este caso, no es necesario ningún mutex (en lo que se refiere a la integridad de va_list). Y no hay necesidad de un objeto reentrante va_list. Tu código está perfectamente bien sin mutexes. Funcionará bien en un entorno de múltiples hilos. Las macros del grupo va_... no funcionan en el "puntero de pila" real. En cambio, operan en un objeto independiente completo va_list que se puede usar para iterar sobre los valores almacenados en la pila. Puedes pensar que es tu propio puntero de pila local privado. Cada hilo que invoca su función obtendrá su propia copia de ese va_list iterando sobre su propia pila. No habrá conflicto entre los hilos.

+1

Sí, dos llamadas de va_start en una sola lista_va invalidarán esa lista. Por lo tanto, para una lista va_ estática (o global, o un miembro de la clase ...), tal vez se invalidaría, pero una llamada futura a la misma función crearía una nueva lista_va. – Nate

+0

@Nate: Sí, pero uno no necesita necesariamente una 'va_list' estática para encontrarse con el problema. Los problemas de este tipo surgen usualmente cuando uno opera en la misma 'va_list' local desde dentro de una sola llamada a la misma función variadic. No hay nada ilegal sobre eso. – AnT

0

Hay algunas plataformas donde va_list tendría problemas con la reentrada, pero en esa misma plataforma todas las variables locales tienen tales problemas. Sin embargo, tengo curiosidad: ¿qué espera hacer tu función de escritura? Si usa parámetros configurados antes de llamar a escritura, eso en sí mismo podría causar problemas de subprocesamiento a menos que (1) cualquier instancia particular del objeto que contenga _write solo sea utilizada por un hilo a la vez, o (2) todos los hilos que usan un objeto para escribir querrán los mismos parámetros de configuración.

+0

_write seguirá estando protegido. Solo quería mover la protección a ese nivel y no tener que preocuparme por todos los punteros de va_list. – Nate