¿Afirmar simple para las llamadas no reentrantes ordenadas?
-
28-10-2019 - |
Pregunta
Tengo dos funciones:
void prepare () y nsoid acabado () que se llamará secuencialmente como:
prepare();
<do something>;
finish();
...
prepare();
<do something>;
finish();
Quiero hacer una afirmación simple para probar simplemente que, de hecho, se les llama de esta manera y que no se les llama simultáneamente o fuera de servicio en la aplicación.
Esta aplicación es una aplicación única. Esta es una simple verificación de cordura de desarrollo/prueba para asegurarse de que estas funciones se llamen en orden y que por cualquier razón, no se llaman simultáneamente. Además, estas afirmaciones/controles de cordura deben omitirse del código de producción, ya que el rendimiento es crucial.
¿Un simple afirmación () le gustaría que este funcione mejor?
int test = 0;
void prepare() {
assert(++test == 1);
.
.
.
}
void finish() {
assert(--test == 0);
.
.
.
}
Solución
Su código está bien, a menos que necesite permitir la anidación prepare
y finish
llamadas.
Si no se permite la anidación, podría usar un bool
en lugar de un int
:
bool locked = false;;
void prepare() {
assert( ! locked );
locked = true;
...
}
void finish() {
assert( locked );
locked = false;
...
}
Otros consejos
Es posible que quieras cambiar
int test = 0;
a
#ifndef NDEBUG
int test = 0;
#endif
Para satisfacer su requisito de que "cualquier código relacionado con esta prueba debe omitirse de la producción".
Probablemente quieras:
int test = 0;
void prepare() {
// enter critical section
assert(test++ == 0);
.
.
.
// leave critical section
}
void finish() {
// enter critical section
assert(--test == 0);
.
.
.
// leave critical section
}
Aquí hay una condición de carrera: dos instancias concurrentes de prepare
podría tomar el valor de test
Al mismo tiempo, ambos lo incrementan en un registro para obtener ambos 1
, luego haga la comparación para obtener true
.
Haciendolo volatile
es no voy a ayudar. En cambio, debes poner un mutex en test
, al igual que:
boost::mutex mtx;
int test = 0;
void prepare()
{
boost::mutex::scoped_try_lock lock(&mtx);
assert(lock.owns_lock());
assert(test++ == 0);
// ...
}
void finish()
{
boost::mutex::scoped_try_lock lock(&mtx);
assert(lock.owns_lock());
assert(--test == 0);
}
Si pones <do something>;
en un class
Puede reducir la necesidad de un cheque:
Solo tenga la llamada del constructor prepare
y la llamada del destructor finish
. Luego se aplica automáticamente que se llaman apropiadamente.
Tenga en cuenta que los problemas de concurrencia y anidación aún se aplican: si desea evitar la anidación, entonces necesitaría algún tipo de estado global (¿miembro de clase estática?) Para realizar un seguimiento de eso, y si se usa en más de un hilo de acceso a eso. El contador necesitaría estar protegido mutex.
También tenga en cuenta que también podría hacer privado operator new/delete
Para evitar que alguien cree uno en el montón y no lo destruya.
Como estás usando C ++, ¿por qué no usar RAII? Todavía debe verificar el uso de reentrantes, pero RAII simplifica las cosas considerablemente. Combinado con Mutex de Larsmans y Eliminación de Raedwald en ndebug:
struct Frobber {
Frobber() {
assert(mtx.try_lock());
#ifndef NDEBUG
try { // in case prepare throws
#endif
prepare();
#ifndef NDEBUG
}
catch (...) {
mtx.unlock();
throw;
}
#endif
}
void something();
// And the other actions that can be performed between preparation and finishing.
~Frobber() {
finish();
#ifndef NDEBUG
mtx.unlock();
#endif
}
private:
#ifndef NDEBUG
static boost::mutex mtx;
#endif
Frobber(Frobber const&); // not defined; 0x: = delete
Frobber& operator=(Frobber const&); // not defined; 0x: = delete
};
#ifndef NDEBUG
boost::mutex Frobber::mtx;
#endif
void example() {
Frobber blah; // instead of prepare()
blah.something();
// implicit finish()
}
Dentro de ejemplo, simplemente no poder Haga algo sin prepararse primero, y el final siempre sucederá, incluso si se lanza una excepción.
Nota al margen sobre ndebug: si lo usa de esta manera, asegúrese de que siempre esté definido o siempre indefinido en todos Unidades de traducción, a diferencia de cómo se usa para afirmar (lo que permite definir y indefinir en varios puntos).