ejemplos ¿Son las excepciones de C++ suficientes para implementar el almacenamiento local de subprocesos?




dev c++ (4)

Acceder a los datos en la función actual de la pila de llamadas siempre es seguro para subprocesos. Es por eso que su código es seguro para subprocesos, no por el uso inteligente de las excepciones. El almacenamiento local de subprocesos nos permite almacenar datos por subproceso y hacer referencia a ellos fuera de la pila de llamadas inmediata.

Estaba comentando en una respuesta que el almacenamiento local de subprocesos es agradable y recordé otra discusión informativa sobre las excepciones donde supuse

Lo único especial sobre el entorno de ejecución dentro del bloque de lanzamiento es que el objeto de excepción es referenciado por rethrow.

Poner dos y dos juntos, ¿no ejecutaría un subproceso completo dentro de un bloque de captura de funciones de su función principal con almacenamiento de subprocesos local?

Parece funcionar bien, aunque lentamente. ¿Es esta novela o bien caracterizada? ¿Hay otra manera de resolver el problema? Fue mi premisa inicial correcta? ¿En qué tipo de gastos get_thread incurre get_thread en su plataforma? ¿Cuál es el potencial de optimización?

#include <iostream>
#include <pthread.h>
using namespace std;

struct thlocal {
    string name;
    thlocal( string const &n ) : name(n) {}
};

struct thread_exception_base {
    thlocal &th;
    thread_exception_base( thlocal &in_th ) : th( in_th ) {}
    thread_exception_base( thread_exception_base const &in ) : th( in.th ) {}
};

thlocal &get_thread() throw() {
    try {
        throw;
    } catch( thread_exception_base &local ) {
        return local.th;
    }
}

void print_thread() {
    cerr << get_thread().name << endl;
}

void *kid( void *local_v ) try {
    thlocal &local = * static_cast< thlocal * >( local_v );
    throw thread_exception_base( local );
} catch( thread_exception_base & ) {
    print_thread();

    return NULL;
}

int main() {
    thlocal local( "main" );
    try {
        throw thread_exception_base( local );
    } catch( thread_exception_base & ) {
        print_thread();

        pthread_t th;
        thlocal kid_local( "kid" );
        pthread_create( &th, NULL, &kid, &kid_local );
        pthread_join( th, NULL );

        print_thread();
    }

    return 0;
}

Esto requiere definir nuevas clases de excepción derivadas de thread_exception_base , inicializar la base con get_thread() , pero en general esto no se siente como un insomnio improductivo dominado por la mañana ...

EDITAR: Parece que GCC hace tres llamadas a pthread_getspecific en get_thread . EDITAR: y mucha desagradable introspección en la pila, el entorno y el formato ejecutable para encontrar el bloque de catch que me perdí en el primer recorrido. Esto parece altamente dependiente de la plataforma, ya que GCC está llamando a algunos libunwind desde el sistema operativo. Gastos generales del orden de 4000 ciclos. Supongo que también tiene que atravesar la jerarquía de clases, pero eso se puede mantener bajo control.


Answer #1

Creo que estás en algo aquí. Esto podría ser incluso una forma portátil de convertir datos en devoluciones de llamada que no acepten una variable de "estado" de usuario, como ha mencionado, incluso aparte de cualquier uso explícito de subprocesos.

Entonces suena como si hubieras contestado la pregunta en tu tema: SÍ.


Answer #2

En el espíritu lúdico de la pregunta, ofrezco esta creación de pesadilla horrible:

class tls
{
    void push(void *ptr)
    {
        // allocate a string to store the hex ptr 
        // and the hex of its own address
        char *str = new char[100];
        sprintf(str, " |%x|%x", ptr, str);
        strtok(str, "|");
    }

    template <class Ptr>
    Ptr *next()
    {
        // retrieve the next pointer token
        return reinterpret_cast<Ptr *>(strtoul(strtok(0, "|"), 0, 16));
    }

    void *pop()
    {
        // retrieve (and forget) a previously stored pointer
        void *ptr = next<void>();
        delete[] next<char>();
        return ptr;
    }

    // private constructor/destructor
    tls() { push(0); }
    ~tls() { pop(); }

public:
    static tls &singleton()
    {
        static tls i;
        return i;
    }

    void *set(void *ptr)
    {
        void *old = pop();
        push(ptr);
        return old;
    }

    void *get()
    {
        // forget and restore on each access
        void *ptr = pop();
        push(ptr);
        return ptr;
    }
};

Aprovechando el hecho de que, de acuerdo con el estándar de C ++, strtok guarda su primer argumento para que las llamadas posteriores puedan pasar 0 para recuperar más tokens de la misma cadena, por lo tanto, en una implementación que tenga en cuenta un subproceso, debe usar TLS.

example *e = new example;

tls::singleton().set(e);

example *e2 = reinterpret_cast<example *>(tls::singleton().get());

Entonces, siempre que no se use strtok de la manera prevista en cualquier otra parte del programa, tenemos otra ranura TLS de repuesto.


Answer #3
void *kid( void *local_v ) try {
    thlocal &local = * static_cast< thlocal * >( local_v );
    throw local;
} catch( thlocal & ) {
    print_thread();

    return NULL;
}

==

void *kid (void *local_v ) { print_thread(local_v); }

Puede que me esté faltando algo aquí, pero no es un almacenamiento local de subprocesos, solo un paso de argumentos innecesariamente complicado. El argumento es diferente para cada subproceso solo porque se pasa a pthread_create, no debido a ninguna excepción de malabares.

Resultó que, de hecho, faltaba que GCC esté produciendo llamadas de almacenamiento local de subprocesos reales en este ejemplo. En realidad, hace que el tema sea interesante. Todavía no estoy seguro de si es un caso para otros compiladores y en qué se diferencia de llamar directamente al almacenamiento de subprocesos.

Todavía sostengo mi argumento general de que se puede acceder a los mismos datos de una manera más simple y directa, ya sea argumentos, caminatas en pilas o almacenamiento local de subprocesos.





thread-local