Análisis del código fuente de Skynet: operación y trabajador del hilo principal y cola de mensajes

proceso de inicio de skynet

El programa skynet solo tiene el directorio skynet-src. ./Skynet ./example/config inicia
skynet_main.c para leer el archivo de configuración, establecer variables de entorno y llamar a la función skynet_start de skynet_start.c

La función skynet_start es el servicio básico inicial, llamando a la función _start para iniciar el subproceso _timer, el subproceso del socket, el subproceso _monitor y los múltiples subprocesos _worker configurados para comenzar a trabajar

mecanismo operativo skynet

Lo más importante para cada servicio de skynet es establecer una función de devolución de llamada. Cuando un servicio envía un mensaje a otro servicio, será presionado en la cola de mensajes de este servicio, esperando que el hilo _worker saque una cola de mensajes del servicio. de la cola global y luego sacarlo de esta cola de mensajes. Un mensaje se procesa usando la devolución de llamada de este servicio. El
servidor del juego generalmente inicia un servicio de socket para enseñar la administración de gate.so. El servicio de socket puede aceptar mensajes externos y pasar al servicio interno para su procesamiento.

monitor

El monitoreo del servicio de Skynet es relativamente simple. Desde un principio de diseño, esto también es correcto, porque la capa de marco básicamente puede informar y registrar, y el negocio de nivel superior está cambiando mucho, no importa cómo lo escriba, es posible que no cumpla con el necesidades comerciales del nivel superior. El seguimiento de los servicios en skynet se implementa en skynet_monitor.cy skynet_monitor.h. Cuando el servicio puede caer en un bucle infinito, se crea un log.

Cada vez que se envía un mensaje se llama a skynet_monitor_trigger, el cual se ajusta dos veces, la primera vez que los parámetros origen y destino son valores reales, es decir, no son 0. El segundo ajuste es cuando se completa la distribución del mensaje, tanto el origen como el destino se asignan a 0.

Si la distribución del mensaje no se completa después de la primera llamada de activación, el hilo del monitor verifica por primera vez y asigna el valor de check_version a la versión. Luego, el hilo del monitor verifica por segunda vez. En este momento, la versión y check_version serán iguales, y el destino no es 0 en este momento, y entrará en el proceso de liberar el servicio de destino e imprimir la alarma.

Temporizador

El temporizador de Skynet es una función que se utiliza con frecuencia en los juegos y tiene sentido analizar su código fuente. Además, el código fuente principal de C se ha analizado básicamente, excepto por el temporizador y la red. Otros están relacionados con lua c api, o interactúan más con lua. El código fuente del temporizador está en skynet-timer.cy skynet-timer.h.
El temporizador externo de Skynet se almacena en dos partes, una parte se almacena en una matriz llamada cerca y la otra parte se almacena en una matriz bidimensional, dividida en cuatro niveles.
Hay un hilo de temporizador en skynet, que actualiza el tiempo en el temporizador cada 2,5 milisegundos. Cada actualización agregará 1 a un contador llamado tiempo, por lo que este contador puede realmente tratarse como tiempo, y luego se activa el temporizador en la matriz cercana.

1.función principal

La función principal es el punto de entrada del proceso skynet y necesita una ruta de archivo de configuración como parámetro. La función principal primero hace un trabajo de asignación de memoria y luego carga el contenido en el archivo de configuración. Luego, establezca el contenido del archivo de configuración en la variable de entorno lua _ENV. Luego lea la configuración de _ENV a la configuración c. Finalmente, se llama skynet_start.

Función 2.skynet_start

La función skynet_start determina si el servicio se inicia en segundo plano según la configuración, luego inicializa las estructuras de datos de timer / socket / module / mq, les asigna memoria y completa algunos campos necesarios. El siguiente paso es crear y registrar un servicio de registro de registros, que es implementado por la capa C. Luego cree y registre el servicio snax y realice la operación de arranque. Finalmente, se llama a la función de inicio.

3.función de inicio

Esta función iniciará el subproceso de temporizador / socket / monitor / trabajador. Solo hay un subproceso de temporizador / socket / monitor, pero hay varios subprocesos de trabajo, que se configuran en el archivo de configuración con thread = num. Si no hay configuración en el archivo de configuración, hay 8. El primer hilo de trabajo tiene su propio valor de peso. Cuanto mayor sea el valor de peso, menor será el intervalo de tiempo para un solo servicio.

Una vez iniciados los subprocesos, ingresan pthread_join y esperan la terminación de los subprocesos. Después de que terminen todos los hilos, se cerrará skynet.

La cola de mensajes es una de las funciones centrales de skynet. Para decirlo sin rodeos, su función es entrar y salir de la cola, primero en entrar, primero en salir. Esta estructura de datos ha sido discutida. El código fuente está implementado en skynet_mq.hy skynet_mq.c. Echemos un vistazo a estos dos archivos.

#ifndef SKYNET_MESSAGE_QUEUE_H
#define SKYNET_MESSAGE_QUEUE_H

#include <stdlib.h>
#include <stdint.h>
//消息结构体
struct skynet_message {
    
    
    uint32_t source; //从哪里来
    int session; //参考云风的博客,这个session是个标识
    void * data; //消息体
    size_t sz;//长度
};

// type is encoding in skynet_message.sz high 8bit
#define MESSAGE_TYPE_MASK (SIZE_MAX >> 8)
#define MESSAGE_TYPE_SHIFT ((sizeof(size_t)-1) * 8)

struct message_queue;
//全局消息入队
void skynet_globalmq_push(struct message_queue * queue);
//全局消息出队
struct message_queue * skynet_globalmq_pop(void);
//创建一个消息队列
struct message_queue * skynet_mq_create(uint32_t handle);
void skynet_mq_mark_release(struct message_queue *q);
//消息移除
typedef void (*message_drop)(struct skynet_message *, void *);
//队列释放
void skynet_mq_release(struct message_queue *q, message_drop drop_func, void *ud);
//消息处理者handler
uint32_t skynet_mq_handle(struct message_queue *);

// 0 for success
//消息出队
int skynet_mq_pop(struct message_queue *q, struct skynet_message *message);
//消息入队
void skynet_mq_push(struct message_queue *q, struct skynet_message *message);

// return the length of message queue, for debug
int skynet_mq_length(struct message_queue *q);
int skynet_mq_overload(struct message_queue *q);

void skynet_mq_init();

#endif

上面有价值的东西实际就是消息的结构体,其它都是声明而已。

//默认队列长度为64
#define DEFAULT_QUEUE_SIZE 64
//最大长度为max(16bit)+1=65536
#define MAX_GLOBAL_MQ 0x10000

// 0 means mq is not in global mq.
// 1 means mq is in global mq , or the message is dispatching.

#define MQ_IN_GLOBAL 1
#define MQ_OVERLOAD 1024

struct message_queue {
    
    
    struct spinlock lock;
    uint32_t handle;  //目标handler
    int cap; //容量
    int head; //头位置
    int tail; //末尾位置
    int release; //释放标记
    int in_global; //是否在全局队列中
    int overload; //最大负载
    int overload_threshold; //最大负载阀值
    struct skynet_message *queue; //循环数组
    struct message_queue *next; //下一个队列,链表
};
//全局消息队列,链表
struct global_queue {
    
    
    struct message_queue *head; //头
    struct message_queue *tail; //尾
    struct spinlock lock;
};

static struct global_queue *Q = NULL;

void 
skynet_globalmq_push(struct message_queue * queue) {
    
    
    struct global_queue *q= Q;

    SPIN_LOCK(q)
    assert(queue->next == NULL);
    if(q->tail) {
    
     //链表不为空
        q->tail->next = queue;
        q->tail = queue;
    } else {
    
     //链表为空
        q->head = q->tail = queue;
    }
    SPIN_UNLOCK(q)
}

//取链表中第一个消息队列
struct message_queue * 
skynet_globalmq_pop() {
    
    
    struct global_queue *q = Q;

    SPIN_LOCK(q)
    struct message_queue *mq = q->head;
    if(mq) {
    
    
         //注意这里,队列取出来后,就从链表中删除了
        q->head = mq->next;
        if(q->head == NULL) {
    
    
            assert(mq == q->tail);
            q->tail = NULL;
        }
        mq->next = NULL;
    }
    SPIN_UNLOCK(q)

    return mq;
}
//创建一个消息队列
struct message_queue * 
skynet_mq_create(uint32_t handle) {
    
    
    struct message_queue *q = skynet_malloc(sizeof(*q));
    q->handle = handle;
    q->cap = DEFAULT_QUEUE_SIZE;
    q->head = 0;//刚开始头为0
    q->tail = 0;//刚开始尾也为0
    SPIN_INIT(q)
    // When the queue is create (always between service create and service init) ,
    // set in_global flag to avoid push it to global queue .
    // If the service init success, skynet_context_new will call skynet_mq_push to push it to global queue.
    q->in_global = MQ_IN_GLOBAL;
    q->release = 0;
    q->overload = 0;
    q->overload_threshold = MQ_OVERLOAD;
    ///这里分配的是数组,是数组,是数组
    q->queue = skynet_malloc(sizeof(struct skynet_message) * q->cap);
    q->next = NULL;

    return q;
}
//释放队列,回收内存
static void 
_release(struct message_queue *q) {
    
    
    assert(q->next == NULL);
    SPIN_DESTROY(q)
    skynet_free(q->queue);
    skynet_free(q);
}
//返回队列的handler
uint32_t 
skynet_mq_handle(struct message_queue *q) {
    
    
    return q->handle;
}
//获取队列长度,注意数组被循环使用的情况
int
skynet_mq_length(struct message_queue *q) {
    
    
    int head, tail,cap;

    SPIN_LOCK(q)
    head = q->head;
    tail = q->tail;
    cap = q->cap;
    SPIN_UNLOCK(q)
    //当还没有循环使用数组的时候
    if (head <= tail) {
    
    
        return tail - head;
    }
    //当数组已经被循环使用的时候
    return tail + cap - head;
}
//获取负载情况
int
skynet_mq_overload(struct message_queue *q) {
    
    
    if (q->overload) {
    
    
        int overload = q->overload;
        q->overload = 0; //这里清零是为了避免持续产生报警,在skynet-server.c中
        return overload;
    } 
    return 0;
}

//消息队列出队,从数组中出队
int
skynet_mq_pop(struct message_queue *q, struct skynet_message *message) {
    
    
    int ret = 1;
    SPIN_LOCK(q)
    //说明队列不是空的
    if (q->head != q->tail) {
    
    
        *message = q->queue[q->head++];  //注意head++,数据不移动,移动的是游标
        ret = 0;
        int head = q->head;
        int tail = q->tail;
        int cap = q->cap;
    //因为是循环数组,超出边界后要重头开始,所以设为0
        if (head >= cap) {
    
    
            q->head = head = 0;
        }
        //如果数组被循环使用了,那么tail < head
        int length = tail - head;
        if (length < 0) {
    
    
            length += cap;
        }
        //长度要超过阀值了,扩容一倍,和c++的vector一样的策略
        while (length > q->overload_threshold) {
    
    
            q->overload = length;
            q->overload_threshold *= 2;
        }
    } else {
    
     //队列是空的
        // reset overload_threshold when queue is empty
        q->overload_threshold = MQ_OVERLOAD;
    }

    if (ret) {
    
    
        q->in_global = 0;
    }
    
    SPIN_UNLOCK(q)

    return ret;
}

//为了方便和上面的函数对比,我把skynet_mq_push提上来了
void 
skynet_mq_push(struct message_queue *q, struct skynet_message *message) {
    
    
    assert(message);
    SPIN_LOCK(q)
//入队
    q->queue[q->tail] = *message;
//因为是循环数组,越界了要重头开始
    if (++ q->tail >= q->cap) {
    
    
        q->tail = 0;
    }
//如果首尾重叠了,要扩展
    if (q->head == q->tail) {
    
    
        expand_queue(q);
    }
//重新放回全局队列中
    if (q->in_global == 0) {
    
    
        q->in_global = MQ_IN_GLOBAL;
        skynet_globalmq_push(q);
    }
    
    SPIN_UNLOCK(q)
}
//扩展循环数组
static void
expand_queue(struct message_queue *q) {
    
    
//新建一个数组
    struct skynet_message *new_queue = skynet_malloc(sizeof(struct skynet_message) * q->cap * 2);
    int i;
    for (i=0;i<q->cap;i++) {
    
     //老数据拷过来
        new_queue[i] = q->queue[(q->head + i) % q->cap];
    }
    q->head = 0; //重设head
    q->tail = q->cap; //重设tail
    q->cap *= 2;
    
    skynet_free(q->queue); //释放老数组
    q->queue = new_queue;
}

//初始化全局队列
void 
skynet_mq_init() {
    
    
    struct global_queue *q = skynet_malloc(sizeof(*q));
    memset(q,0,sizeof(*q));
    SPIN_INIT(q);
    Q=q;
}
//服务释放标记
void 
skynet_mq_mark_release(struct message_queue *q) {
    
    
    SPIN_LOCK(q)
    assert(q->release == 0);
    q->release = 1;
    if (q->in_global != MQ_IN_GLOBAL) {
    
    
        skynet_globalmq_push(q);
    }
    SPIN_UNLOCK(q)
}
//释放服务,清空循环数组
static void
_drop_queue(struct message_queue *q, message_drop drop_func, void *ud) {
    
    
    struct skynet_message msg;
    while(!skynet_mq_pop(q, &msg)) {
    
    
        drop_func(&msg, ud);
    }
    _release(q); //回收内存
}
//释放服务相关的队列
void 
skynet_mq_release(struct message_queue *q, message_drop drop_func, void *ud) {
    
    
    SPIN_LOCK(q)
    
    if (q->release) {
    
    
        SPIN_UNLOCK(q)
        _drop_queue(q, drop_func, ud);
    } else {
    
    
        skynet_globalmq_push(q);
        SPIN_UNLOCK(q)
    }
}

El análisis del código ha terminado, se puede ver que en realidad hay dos tipos de colas de mensajes en skynet, una es la cola de mensajes global y la otra es la cola de mensajes del servicio. Cada servicio tiene su propia cola de mensajes y cada cola de mensajes de servicio tiene el identificador de identificador del servicio. Esto implica la distribución de mensajes, así que no lo expandiré aquí. La cola de mensajes global hace referencia a cada cola de mensajes de servicio.

La cola de mensajes global se implementa usando una lista enlazada clásica, mientras que la cola de mensajes de servicio se implementa usando una matriz circular que es relativamente poco intuitiva y puede ser particularmente difícil de entender para algunas personas. Y cuando el espacio de la matriz no sea suficiente, se expandirá dinámicamente y la capacidad se expandirá al doble de la capacidad actual.

El nombre de la función sacar y poner en cola de la cola de mensajes es relativamente simple y claro, push / pop. Este nombre puede traer algunos malentendidos, si se cambia a poner en cola / sacar de cola, está más en línea con su función real.

Recomendar a todos un campo de entrenamiento sobre el combate real del proyecto skynet. La inscripción ahora es equivalente a gratuita. El contenido principal:


Cola de mensajes de programación simultánea de varios núcleos .
Programación de mensajes del actor del grupo de subprocesos
Implementación del módulo de red Implementación del
temporizador de rueda de tiempo
Programación de interfaz lua / c / Programación de
conceptos básicos de programación Skynet
demuestra el pensamiento de programación del actor

Dirección: Haga clic para ver el video
Más información sobre skynet más grupo: 832218493 ¡Consígalo gratis!
Inserte la descripción de la imagen aquí

Supongo que te gusta

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