Gestión del ciclo de vida del actor del código fuente de skynet

Skynet se basa en multiproceso. Cada actor será programado por un hilo separado, y cada actor puede matar a otros actores, enviar mensajes a otros actores, crear actores, es decir, un actor puede estar retenido por múltiples hilos, luego hay tres problemas. :

Cuando se utiliza un actor al mismo tiempo, cómo liberarlo de forma segura.
Después de que se libera al actor, cómo detectar que el actor no es válido para uso externo para que el proceso pueda continuar.
Si el mensaje en el buzón tiene semántica de respuesta de solicitud, entonces si se notifica al origen del mensaje.
El marco utiliza el mapeo de manejadores y el recuento de referencias para exponer el manejador de sc (skynet_context), no punteros. Este módulo se implementa en /skynet-src/skynet_handle.c, para analizar sus principios específicos, partiendo de la interfaz del archivo de cabecera:

skynet_handle.h

1 #ifndef SKYNET_CONTEXT_HANDLE_H
 2 #define SKYNET_CONTEXT_HANDLE_H
 3 
 4 #include <stdint.h>
 5 
 6 // reserve high 8 bits for remote id
 7 #define HANDLE_MASK 0xffffff
 8 #define HANDLE_REMOTE_SHIFT 24
 9 
10 struct skynet_context;
11 
12 uint32_t skynet_handle_register(struct skynet_context *);
13 int skynet_handle_retire(uint32_t handle);
14 struct skynet_context * skynet_handle_grab(uint32_t handle);
15 void skynet_handle_retireall();
16 
17 uint32_t skynet_handle_findname(const char * name);
18 const char * skynet_handle_namehandle(uint32_t handle, const char *name);
19 
20 void skynet_handle_init(int harbor);
21 
22 #endif

El identificador es un número entero de uint32_t, y los 8 bits superiores representan el nodo remoto (esta es la función de clúster que viene con el marco, y el análisis posterior ignorará esta parte. Primero, no es el núcleo del marco, y segundo , no se recomienda esta instalación de clúster).

Echemos un vistazo a su estructura de datos interna:

define DEFAULT_SLOT_SIZE 4
#define MAX_SLOT_SIZE 0x40000000

struct handle_name {
    
    
    char * name;
    uint32_t handle;
};

struct handle_storage {
    
    
    struct rwlock lock;

    uint32_t harbor;
    uint32_t handle_index;
    int slot_size;
    struct skynet_context ** slot;
    
    int name_cap;
    int name_count;
    struct handle_name *name;
};

static struct handle_storage *H = NULL;

Parece una matriz de sc y no veo nada. Mira otros métodos, skynet_handle_register:

 1 uint32_t
 2 skynet_handle_register(struct skynet_context *ctx) {
    
    
 3     struct handle_storage *s = H;
 4 
 5     rwlock_wlock(&s->lock);
 6     
 7     for (;;) {
    
    
 8         int i;
 9         for (i=0;i<s->slot_size;i++) {
    
    
10             uint32_t handle = (i+s->handle_index) & HANDLE_MASK;
11             int hash = handle & (s->slot_size-1);
12             if (s->slot[hash] == NULL) {
    
    
13                 s->slot[hash] = ctx;
14                 s->handle_index = handle + 1;
15 
16                 rwlock_wunlock(&s->lock);
17 
18                 handle |= s->harbor;
19                 return handle;
20             }
21         }
22         assert((s->slot_size*2 - 1) <= HANDLE_MASK);
23         struct skynet_context ** new_slot = skynet_malloc(s->slot_size * 2 * sizeof(struct skynet_context *));
24         memset(new_slot, 0, s->slot_size * 2 * sizeof(struct skynet_context *));
25         for (i=0;i<s->slot_size;i++) {
    
    
26             int hash = skynet_context_handle(s->slot[i]) & (s->slot_size * 2 - 1);
27             assert(new_slot[hash] == NULL);
28             new_slot[hash] = s->slot[i];
29         }
30         skynet_free(s->slot);
31         s->slot = new_slot;
32         s->slot_size *= 2;
33     }
34 }

Este método consiste en agregar un mapeo de identificador de sc.

Desde el punto de vista del código, es una especie de mapeo hash, que utiliza bloqueos de lectura y escritura para garantizar la seguridad de los subprocesos. Las líneas 9-21 son el proceso de selección del valor hash, que es un número entero que aumenta automáticamente, que se incrementa en 1 cada vez que se calcula. El handle_index se usa como contador y el módulo se usa para mapear al sc formación. Utilice el segundo método de detección para resolver el conflicto, el bucle de la novena línea asegura que la detección del conflicto cubrirá toda la matriz.

Para la línea 22, significa que la matriz está llena. En este momento, la matriz original se duplicará y el identificador será módulo y se mapeará nuevamente en la nueva matriz. Esta regla hash tiene dos ventajas: 1. El valor hash no se repetirá 2. El proceso de búsqueda es verdaderamente O (1).

A partir de la línea 22 y la modificación de handle_index, podemos saber que esta función se basa en dos premisas: 1. El tamaño de la matriz no excederá 0xffffff. 2. El handle_index no maneja overflow, que puede ser 0, que es se supone que no se desborda. Personalmente, creo que handle_index es mejor para lidiar con la situación de desbordamiento, si es mayor que 0xffffff, establézcalo en 1.

Echemos un vistazo a skynet_handle_grab nuevamente:

1 struct skynet_context * 
 2 skynet_handle_grab(uint32_t handle) {
    
    
 3     struct handle_storage *s = H;
 4     struct skynet_context * result = NULL;
 5 
 6     rwlock_rlock(&s->lock);
 7 
 8     uint32_t hash = handle & (s->slot_size-1);
 9     struct skynet_context * ctx = s->slot[hash];
10     if (ctx && skynet_context_handle(ctx) == handle) {
    
    
11         result = ctx;
12         skynet_context_grab(result);
13     }
14 
15     rwlock_runlock(&s->lock);
16 
17     return result;
18 }

La función de esta función es encontrar el sc correspondiente de acuerdo con el identificador y devolver NULL si el identificador no es válido. La búsqueda es un bloqueo de lectura. El proceso de búsqueda es muy simple. Tome el módulo del identificador y luego juzgue si el identificador del elemento en el índice es consistente. El recuento de referencias está almacenado en sc, no en este módulo. De hecho, debe ser puesto en este módulo para que sea más puro. En sc, solo necesitas saber cómo liberarse. Una búsqueda exitosa aumentará el recuento de sc (skynet_context_grab).

Echemos un vistazo a skynet_handle_retire:

 1 int
 2 skynet_handle_retire(uint32_t handle) {
    
    
 3     int ret = 0;
 4     struct handle_storage *s = H;
 5 
 6     rwlock_wlock(&s->lock);
 7 
 8     uint32_t hash = handle & (s->slot_size-1);
 9     struct skynet_context * ctx = s->slot[hash];
10 
11     if (ctx != NULL && skynet_context_handle(ctx) == handle) {
    
    
12         s->slot[hash] = NULL;
13         ret = 1;
14         int i;
15         int j=0, n=s->name_count;
16         for (i=0; i<n; ++i) {
    
    
17             if (s->name[i].handle == handle) {
    
    
18                 skynet_free(s->name[i].name);
19                 continue;
20             } else if (i!=j) {
    
    
21                 s->name[j] = s->name[i];
22             }
23             ++j;
24         }
25         s->name_count = j;
26     } else {
    
    
27         ctx = NULL;
28     }
29 
30     rwlock_wunlock(&s->lock);
31 
32     if (ctx) {
    
    
33         // release ctx may call skynet_handle_* , so wunlock first.
34         skynet_context_release(ctx);
35     }
36 
37     return ret;
38 }

La función de esta función es desasignar el identificador, no disminuir el recuento de referencias.

Hay dos pasos para una implementación específica: 1. Limpiar la ranura correspondiente al identificador y llamar a skynet_context_release 2. Si hay un nombre registrado, elimine el nodo correspondiente.

De hecho, sería mejor poner el control de la liberación de sc en este módulo.

El método restante es el soporte de nombres de identificadores, el mapeo de nombres se almacena en una matriz, se clasifica en orden lexicográfico y se usa el método de búsqueda binaria al buscar.

Ahora puede ver los escenarios específicos del ciclo de vida de sc, solo mire dos lugares:

En la oficina de despacho de mensajes, en la función skynet_context_message_dispatch,
la interfaz externa de sc es principalmente skynet_command.
Puede verlo en skynet_context_message_dispatch (línea 285 de /skynet-src/skynet_server.c):

struct skynet_context * ctx = skynet_handle_grab(handle);
    if (ctx == NULL) {
    
    
        struct drop_t d = {
    
     handle };
        skynet_mq_release(q, drop_message, &d);
        return skynet_globalmq_pop();
    }

A través de skynet_handle_grab, el SC no es válido, lo que resuelve el problema 2 planteado al principio. Otras interfaces externas de sc también hicieron este juicio.

Entonces el resto es la pregunta 1, la cuestión de la liberación segura. Mire la interfaz de liberación externa de sc, cmd_exit, cmd_kill, que son todos handle_exit:

 1 static void
 2 handle_exit(struct skynet_context * context, uint32_t handle) {
    
    
 3     if (handle == 0) {
    
    
 4         handle = context->handle;
 5         skynet_error(context, "KILL self");
 6     } else {
    
    
 7         skynet_error(context, "KILL :%0x", handle);
 8     }
 9     if (G_NODE.monitor_exit) {
    
    
10         skynet_send(context,  handle, G_NODE.monitor_exit, PTYPE_CLIENT, 0, NULL, 0);
11     }
12     skynet_handle_retire(handle);
13 }

Esta función finalmente llama a skynet_handle_retire, después de que libera el mapeo de manejadores, llama a skynet_context_release.

Eche un vistazo a skynet_context_release:

1 static void 
 2 delete_context(struct skynet_context *ctx) {
    
    
 3     if (ctx->logfile) {
    
    
 4         fclose(ctx->logfile);
 5     }
 6     skynet_module_instance_release(ctx->mod, ctx->instance);
 7     skynet_mq_mark_release(ctx->queue);
 8     CHECKCALLING_DESTROY(ctx)
 9     skynet_free(ctx);
10     context_dec();
11 }
12 
13 struct skynet_context * 
14 skynet_context_release(struct skynet_context *ctx) {
    
    
15     if (ATOM_DEC(&ctx->ref) == 0) {
    
    
16         delete_context(ctx);
17         return NULL;
18     }
19     return ctx;
20 }

Después de que el recuento de referencias sea 0, se publicará sc, por lo que la pregunta 1 está garantizada de la siguiente manera:

Hay dos situaciones después de llamar a handle_exit:

1. Otros flujos lógicos ya han adquirido sc, por lo que el conteo de referencia debe ser mayor que 0, y sc no se liberará en este momento.Se liberará cuando el último flujo lógico disminuya el conteo de referencia, lo cual es seguro.

2. El sc se libera y otros flujos lógicos comienzan skynet_handle_grab. Debido a que se eliminó el mapeo de identificadores, todas las búsquedas no son válidas. El flujo lógico puede saber esto y emitir un juicio, lo cual es seguro.

Cuando se libera sc, el buzón de correo (message_queue) no se libera, solo se llama a skynet_mq_mark_release para establecer el indicador de liberación, entonces, ¿dónde se libera? Primero pensemos en tal situación. Si sc se libera y el buzón no se libera, entonces skynet_handle_grab no podrá encontrar y el buzón seguirá en la cola de nivel 1. Entonces el lugar de liberación solo puede estar en skynet_context_message_dispatch. Veamos Se llama el buzón liberado por skynet_mq_release en la rama donde se considera que sc no es válido.

¿Por qué debería liberarse el buzón por separado y no junto con sc? Debido a que sc se libera mediante el recuento de referencias, el tiempo de liberación no está claro y puede estar en cualquier flujo lógico. Es imposible juzgar si debe retroceder a la cola de nivel 1 en la programación de mensajes, por lo que debe ser independiente .

Solo queda el problema 3. Solo necesita ver cómo se procesa el mensaje cuando se libera el buzón. En el drop_message de /skynet-src/skynet_server.c:

static void
drop_message(struct skynet_message *msg, void *ud) {
    
    
    struct drop_t *d = ud;
    skynet_free(msg->data);
    uint32_t source = d->handle;
    assert(source);
    // report error to the message source
    skynet_send(NULL, source, msg->source, PTYPE_ERROR, 0, NULL, 0);
}

Se resuelve enviando un PTYPE_ERROR a la fuente del mensaje, de modo que el sc que espera recibir la respuesta tenga la oportunidad de finalizar el proceso suspendido. Pero tengo una pregunta, ¿por qué no traes la sesión al responder? ¿Tienes que buscar el buzón por la fuente? Mire esto cuando se distribuyan las noticias.

Si no hay gc, entonces en la programación de subprocesos múltiples, cómo liberar recursos de manera segura es un problema que debe enfrentarse. Suele resolverse de forma independiente en otro módulo, existen dos métodos comunes:

Maneje el mapeo y el recuento de referencias en este artículo. Los punteros inteligentes generalmente se usan en c ++, y el recuento de referencias se agrega y resta automáticamente a través del destructor y el constructor de copias como garantía obligatoria. Personalmente creo que el primero es más flexible.
Marque solo cuando se liberen y recupere recursos periódicamente con una frecuencia determinada.

Haga clic para aprender sobre skynet
Inserte la descripción de la imagen aquí
ahora Para obtener más información sobre skynet, por favor únase al grupo: 832218493 ¡gratis!

Supongo que te gusta

Origin blog.csdn.net/lingshengxueyuan/article/details/111653972
Recomendado
Clasificación