skynet 線程

啓動
創建一個socket、timer、monitor線程和n個工作線程。工作線程的個數由啓動時配置的參數決定。

static void
start(int thread) {
    pthread_t pid[thread+3];

    struct monitor *m = skynet_malloc(sizeof(*m));
    memset(m, 0, sizeof(*m));
    m->count = thread;
    m->sleep = 0;

    m->m = skynet_malloc(thread * sizeof(struct skynet_monitor *));
    int i;
    for (i=0;i<thread;i++) {
        m->m[i] = skynet_monitor_new();
    }
    if (pthread_mutex_init(&m->mutex, NULL)) {
        fprintf(stderr, "Init mutex error");
        exit(1);
    }
    if (pthread_cond_init(&m->cond, NULL)) {
        fprintf(stderr, "Init cond error");
        exit(1);
    }
    //創建監控、timer、socket線程
    create_thread(&pid[0], thread_monitor, m);
    create_thread(&pid[1], thread_timer, m);
    create_thread(&pid[2], thread_socket, m);

    static int weight[] = { 
        -1, -1, -1, -1, 0, 0, 0, 0,
        1, 1, 1, 1, 1, 1, 1, 1, 
        2, 2, 2, 2, 2, 2, 2, 2, 
        3, 3, 3, 3, 3, 3, 3, 3, };
    //創建n個工作線程
    struct worker_parm wp[thread];
    for (i=0;i<thread;i++) {
        wp[i].m = m;
        wp[i].id = i;
        if (i < sizeof(weight)/sizeof(weight[0])) {
            wp[i].weight= weight[i];
        } else {
            wp[i].weight = 0;
        }
        create_thread(&pid[i+3], thread_worker, &wp[i]);
    }

    for (i=0;i<thread+3;i++) {
        pthread_join(pid[i], NULL); 
    }

    free_monitor(m);
}


工作線程工作流程:

static void *
thread_worker(void *p) {
    struct worker_parm *wp = p;
    int id = wp->id;
    int weight = wp->weight;
    struct monitor *m = wp->m;
    struct skynet_monitor *sm = m->m[id];
    skynet_initthread(THREAD_WORKER);
    struct message_queue * q = NULL;
    while (!m->quit) {
        q = skynet_context_message_dispatch(sm, q, weight);
        if (q == NULL) {
            if (pthread_mutex_lock(&m->mutex) == 0) {
                ++ m->sleep;
                // "spurious wakeup" is harmless,
                // because skynet_context_message_dispatch() can be call at any time.
                if (!m->quit)
                    pthread_cond_wait(&m->cond, &m->mutex);
                -- m->sleep;
                if (pthread_mutex_unlock(&m->mutex)) {
                    fprintf(stderr, "unlock mutex error");
                    exit(1);
                }
            }
        }
    }
    return NULL;
}

struct message_queue * 
skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight) {
    ......
    for (i=0;i<n;i++) {
        if (skynet_mq_pop(q,&msg)) {
            skynet_context_release(ctx);
            return skynet_globalmq_pop();
        } else if (i==0 && weight >= 0) {
            n = skynet_mq_length(q);
            n >>= weight;
        }
        ......
    }
    ......
}


weight 用與決定每個工作線程每次從服務的次級消息隊列中取出消息的個數。

僞代碼:

while(true)
    //主消息隊列爲空
    if main_msg_queue:is_empty()
        wait
    end
    //每次從主消息隊列中取出一個次級消息隊列
    ms_queue = main_msg_queue:pop()
    //然後依據線程權重參數從次級消息隊列中取出一定數量的消息
    for(n)
        message = ms_queue:pop
        //調用服務的callback進行處理
        handle:callback(message)
    end
    //處理完如果還有消息隊列部位空,插到主消息隊列的尾部
    if not ms_queue:is_empty()
        main_msg_queue:push(ms_queue)
    end
end

工作線程:每次從主消息隊列中取出一個次級消息隊列。然後依據線程權重參數從次級消息隊列中取出一定數量的消息。調用服務的callback進行處理。  處理完如果次級消息隊列不爲空,則插到主消息隊列的尾部。線程繼續重複上述操作直到住消息隊列爲空。


監控線程:

struct skynet_monitor {
    int version;
    int check_version;
    uint32_t source;
    uint32_t destination;
};
static void *
thread_monitor(void *p) {
    struct monitor * m = p;
    int i;
    int n = m->count;
    skynet_initthread(THREAD_MONITOR);
    for (;;) {
        CHECK_ABORT
        for (i=0;i<n;i++) {
            skynet_monitor_check(m->m[i]);
        }
        for (i=0;i<5;i++) {
            CHECK_ABORT
            sleep(1);
        }
    }

    return NULL;
}
//監控線程檢測是否陷入死循環
void 
skynet_monitor_check(struct skynet_monitor *sm) {
    if (sm->version == sm->check_version) {
        if (sm->destination) {
            skynet_context_endless(sm->destination);
            skynet_error(NULL, "A message from [ :%08x ] to [ :%08x ] maybe in an endless loop (version = %d)", sm->source , sm->destination, sm->version);
        }
    } else {
        sm->check_version = sm->version;
    }
}
//工作線程記錄消息執行記錄
skynet_monitor_trigger(struct skynet_monitor *sm, uint32_t source, uint32_t destination) {
    sm->source = source;
    sm->destination = destination;
    ATOM_INC(&sm->version);
}
//調用消息回調前
skynet_monitor_trigger(sm, msg.source , handle);
//調用消息回調後
skynet_monitor_trigger(sm, 0,0);


每個工作線程對應一個skynet_monitor結構,當工作線程處理消息前用skynet_monitor記錄消息的來源和目標。處理完清除。監控線程每隔五秒檢測。

如何判斷是否陷入死循環?
假設 version = 0,check_version = 0. 工作線程處理消息前 在skynet_monitor_trigger中sm->version = sm->version + 1,五秒後監控線程執行sm->check_version = sm->version; version = 1,check_version = 1.五秒後,監控線程再次檢測,如果在五秒內工作線程執行完消息那麼工作線程會執行skynet_monitor_trigger sm->version = sm->version + 1 .version = 2,check_version = 1.沒執行完則version = 1,check_version = 1,sm->destination != 0.則認爲消息的執行超過了五秒,可能陷入死循環。


timer線程:時間更新,定時檢測,只要有線程sleep 就喚醒,向服務的消息隊列中壓入消息。

socket:線程後邊講解

當timer線程和socket向服務壓入了消息 會調用wakeup喚醒工作線程。因爲此時可能所有的工作線程都在睡眠狀態。如果是服務見發送消息,不用喚醒,因爲此時一定至少有一個工作線程在工作(服務代碼可以執行依賴於線程的執行)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章