• skynet源码分析之skynet_monitor


    使用skynet框架中,偶尔会遇到A message from [ :0000000b ] to [ :0000000c ] maybe in an endless loop (version = 13187)类似的error,意思是0000000c服务处理0000000b服务发过来的某条消息时可能陷入死循环。出现这种error的原因:业务层发生死循环或者比较耗时(超过5s)。这就是skyent_monitor的作用。

    1. skynet启动时会启动一个monitor线程,用来监测各个工作线程。5s循环一次,调用skynet_monitor_check()检测工作线程,稍后说明。

     1 // skynet-src/skynet_start.c
     2 static void *
     3 thread_monitor(void *p) {
     4         struct monitor * m = p;
     5         int i;
     6         int n = m->count;
     7         skynet_initthread(THREAD_MONITOR);
     8         for (;;) {
     9                 CHECK_ABORT
    10                 for (i=0;i<n;i++) {
    11                         skynet_monitor_check(m->m[i]);
    12                 }
    13                 for (i=0;i<5;i++) {
    14                         CHECK_ABORT
    15                         sleep(1);
    16                 }
    17         }
    18 
    19         return NULL;
    20 }

    每个工作线程指定一个skynet_monitor c结构的变量,处理消息前,会记录消息的源地址和目的地址(第5行);处理完消息,清空记录(第7行)。并且会累加version(15行)。

     1 // skynet-src/skynet_server.c
     2 struct message_queue * 
     3 skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight){
     4     ...
     5     skynet_monitor_trigger(sm, msg.source , handle);
     6     dispatch_message(ctx, &msg); //处理消息
     7     skynet_monitor_trigger(sm, 0,0);
     8 }
     9 
    10 // skynet-src/skynet_monitor.c
    11 void 
    12 skynet_monitor_trigger(struct skynet_monitor *sm, uint32_t source, uint32_t destination) {
    13         sm->source = source;
    14         sm->destination = destination;
    15         ATOM_INC(&sm->version);
    16 }

    monitor线程每5s调用一次skynet_monitor_check()检测工作线程:

    没有死循环或者很耗时的操作,version在不断累加,设置check_version等于version(第10行)。

    如果version等于check_version,有两种情况

    1. 这5s期间工作线程没有消息可处理,此时,destination为0

    2. 在处理某条消息时,耗时超过5s,说明有可能死循环了,于是就有了最初的错误日志(第7行)。

     1 // skynet-src/skynet_monitor.c
     2 void 
     3 skynet_monitor_check(struct skynet_monitor *sm) {
     4         if (sm->version == sm->check_version) {
     5                 if (sm->destination) {
     6                         skynet_context_endless(sm->destination);
     7                         skynet_error(NULL, "A message from [ :%08x ] to [ :%08x ] maybe in an endless loop (version = %d)", sm->source , sm->destination, sm->version);
     8                 }
     9         } else {
    10                 sm->check_version = sm->version;
    11         }
    12 }

    2. skynet定义了一个monitor结构

    // skynet-src/skynet_start.c
    struct monitor {
            int count; //工作线程总数
            struct skynet_monitor ** m; //监测各个工作线程
            pthread_cond_t cond; //条件变量
            pthread_mutex_t mutex; //
            int sleep; //休眠的工作线程数
            int quit;
    };

    当没有消息处理(全局消息队列为空)时,工作线程进入休眠(第14行),并且累加m->sleep变量(第10行)。等待socket线程,timer线程唤醒。

     1 // skynet-src/skynet_start.c
     2 static void *
     3 thread_worker(void *p) {
     4         ...
     5         struct message_queue * q = NULL;
     6         while (!m->quit) {
     7                 q = skynet_context_message_dispatch(sm, q, weight);
     8                 if (q == NULL) {
     9                         if (pthread_mutex_lock(&m->mutex) == 0) {
    10                                 ++ m->sleep;
    11                                 // "spurious wakeup" is harmless,
    12                                 // because skynet_context_message_dispatch() can be call at any time.
    13                                 if (!m->quit)
    14                                         pthread_cond_wait(&m->cond, &m->mutex);
    15                                 -- m->sleep;
    16                                 if (pthread_mutex_unlock(&m->mutex)) {
    17                                         fprintf(stderr, "unlock mutex error");
    18                                         exit(1);
    19                                 }
    20                         }
    21                 }
    22         }
    23         return NULL;
    24 }

    第6行,调用pthread_cond_signal唤醒阻塞的工作线程。

    当socket线程接收到数据时,只有当所有工作线程都休眠时才会去唤醒wakeup(m,0)

    当timer定时器线程到达时,只要有工作线程休眠,都会去唤醒wakeup(m,m->count-1),是因为定时器事件需要及时处理。

     1 // skynet-src/skynet_start.c
     2 static void
     3 wakeup(struct monitor *m, int busy) {
     4         if (m->sleep >= m->count - busy) {
     5                 // signal sleep worker, "spurious wakeup" is harmless
     6                 pthread_cond_signal(&m->cond);
     7         }
     8 }
     9 
    10 static void *
    11 thread_socket(void *p) {
    12         struct monitor * m = p;
    13         ...
    14         wakeup(m,0);
    15         return NULL;
    16 }
    17 
    18 static void *
    19 thread_timer(void *p) {
    20         ...
    21         wakeup(m,m->count-1);
    22         ...
    23         return NULL;
    24 }
  • 相关阅读:
    整理:分页存储过程整理
    净利润-流通市值比率”与公司估值
    常见7种股票底部形态(图解)
    nginx
    移动成本分布1
    浅谈公开信息检索和判断能力
    股票技术分析 成交量与换手率专题
    成份股和成份股指数
    股票底部形态初探
    筹码拉抬派发法
  • 原文地址:https://www.cnblogs.com/RainRill/p/9306022.html
Copyright © 2020-2023  润新知