hiredis aeStop僅在redis命令的回調函數中生效 分析

来源:http://www.cnblogs.com/europelee/archive/2016/02/29/5228587.html
-Advertisement-
Play Games

hiredis 是 redis 的client端C語言 lib, hiredis擁有同步和非同步的API, 非同步API的實現有多種方法,分別依賴libev, libevent, libuv, ae等等,其中ae是redis內部實現的一個非同步事件處理模塊。 稍微修改了hiredis的example-ae


hiredis 是 redis 的client端C語言 lib,  hiredis擁有同步和非同步的API, 非同步API的實現有多種方法,分別依賴libev, libevent, libuv, ae等等,其中ae是redis內部實現的一個非同步事件處理模塊。 稍微修改了hiredis的example-ae.c代碼:在一個線程裡面迴圈10次執行命令ping, 檢查redisserver, 如下所示, 線程發完10次ping後,調用disconnect, 發現aeMain函數並未退出,程式一直阻塞住.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <thread>
#include<functional>

#ifdef __cplusplus
    extern "C"{
#endif

#include <hiredis.h>
#include <async.h>
#include <adapters/ae.h>

#ifdef __cplusplus
           }
#endif


/* Put event loop in the global scope, so it can be explicitly stopped */
static aeEventLoop *loop;

void getCallback(redisAsyncContext *c, void *r, void *privdata) {
    redisReply *reply = r;
    if (reply == NULL) return;
    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
}

void connectCallback(const redisAsyncContext *c, int status) {
    if (status != REDIS_OK) {
        printf("Error: %s\n", c->errstr);
        aeStop(loop);
        return;
    }

    printf("Connected...\n");
}

void disconnectCallback(const redisAsyncContext *c, int status) {
    if (status != REDIS_OK) {
        printf("Error: %s\n", c->errstr);
        aeStop(loop);
        return;
    }

    printf("Disconnected...\n");
    aeStop(loop);
}

void quitConnCallBack(redisAsyncContext *c, void *r, void *privdata) {
    printf("quit");
    redisAsyncDisconnect(c);
}

void testThreadLoop(void * p) {
    static int num = 10;
    char c11[64];
    strcpy(c11, "test");
    while(1) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1500));
        num--;
        if (num < 0) {

            //在這裡調用disconnect, 並不能使aeMain退出
            redisAsyncDisconnect((redisAsyncContext *)p);
            //正確做法,應該調用如下
       //redisAsyncCommand((redisAsyncContext *)p, quitConnCallBack, c11, "quit");
            printf("exit\n");
            return;
        }

        redisAsyncCommand((redisAsyncContext *)p, getCallback, c11, "ping");
    }
}

int main (int argc, char **argv) {
    signal(SIGPIPE, SIG_IGN);

    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
    if (c->err) {
        /* Let *c leak for now... */
        printf("Error: %s\n", c->errstr);
        return 1;
    }

    loop = aeCreateEventLoop(64);
    redisAeAttach(loop, c);
    redisAsyncSetConnectCallback(c,connectCallback);
    redisAsyncSetDisconnectCallback(c,disconnectCallback);

    std::thread t(testThreadLoop, c);
    t.detach();
    aeMain(loop);
    return 0;
} 
首先檢查下兩個主要函數aeStop, aeMain的邏輯:   aeStop, aeMain函數代碼如下:
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}
void aeStop(aeEventLoop *eventLoop) {
    eventLoop->stop = 1;
}
  1. stop分析 aeStop僅設置stop標誌為true, aeMain裡面在一直迴圈處理事件,第一印象是,直接設了stop為true後,aeMain在處理完事件後,跳出aeProcessEvents函數後,檢查stop為true就會跳出while迴圈。但是事實是aeMain並未跳出迴圈,難道因為是不同線程間操作,要將stop設置為volatile類型?嘗試修改了stop為volatile int類型,測試結果:aeMain 仍然未推出,程式阻塞,無法推出。

 

2.aeProcessEvents分析 這時就只能推測由於aeProcessEvents沒有退出,導致aeMain執行無法檢測stop值,分析該函數,推測可能阻塞在aeApiPoll函數,同時發現tvp變數是個NULL, 查看aeApiPoll代碼(ae_epoll.c),如下
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
     //...
     //tvp 會是NULL, 推測阻塞在aeApiPoll, 查看aeApiPoll代碼進行證實
        numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int rfired = 0;

        /* note the fe->mask & mask & ... code: maybe an already processed
             * event removed an element that fired and we still didn't
             * processed, so we check if the event is still valid. */
            if (fe->mask & mask & AE_READABLE) {
                rfired = 1;
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            }
            if (fe->mask & mask & AE_WRITABLE) {
                if (!rfired || fe->wfileProc != fe->rfileProc)
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
            }
            processed++;
        }
    }
    /* Check time events */
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);

    return processed; /* return the number of processed file/time events */
}
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

  //真相在這邊,epoll_wait, 第三個參數為-1, epoll_wait將一直等待下去!
    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    if (retval > 0) {
        int j;

        numevents = retval;
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events+j;

            if (e->events & EPOLLIN) mask |= AE_READABLE;
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    }
    return numevents;
}

 

整理下aeMain的流程如下圖所示,

                      

 

我們的disconnect回調, 內部調用 aeStop函數,如果剛好發生在processEvents之後,aeMain檢查stop值之前,那麼就沒問題,當然這種概率極其小,如果這都中了,那可以買彩票了~~,現在我們知道aestop調用是有立即生效的限制範圍,我們最好在processEvents的時候,判斷是否應該退出aeMain, 如果是就調用aeStop. processEvents內部會調用到我們外部定義的各種命令的回調函數, 剛好redis有個quit的命令(讓redisserver關閉連接), 我們就增加一個quit命令回調函數調用aeStop: redisAsyncCommand((redisAsyncContext *)p, quitConnCallBack, c11, "quit");  
void quitConnCallBack(redisAsyncContext *c, void *r, void *privdata) {
    printf("quit");
    redisAsyncDisconnect(c);
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 獲取【下載地址】 QQ: 313596790 【免費支持更新】支持三大資料庫 mysql oracle sqlsever 更專業、更強悍、適合不同用戶群體【新錄針對本系統的視頻教程,手把手教開發一個模塊,快速掌握本系統】A 代碼生成器(開發利器); 增刪改查的處理類,service層,mybatis
  • 游標(Cursor)是處理數據的一種方法,為了查看或者處理結果集中的數據,游標提供了在結果集中一次以行或者多行前進或向後瀏覽數據的能力。我們可以把游標當作一個指針,它可以指定結果中的任何位置,然後允許用戶對指定位置的數據進行處理。 1.游標的組成 游標包含兩個部分:一個是游標結果集、一個是游標位置。
  • null是什麼? 不知道。我是說,他的意思就是不知道(unknown)。 它和true、false組成謂詞的三個邏輯值,代表“未知”。與true和false相比,null最難以令人捉摸,因為它沒有明確的值,在不同的場景下,它能代表不同的含義。下文以例子的方式給大家分享下null使用的典型場景及對應的
  • 在工作中我碰到這樣一個問題,session表需要用到timestamp的欄位,在進行timestamp欄位更新時出現了為題,比如需要對session的有效期增加1小時。採用 systimestamp + 1/24 會丟失秒後的精度,感覺增加之後就變成了date型的樣子。經過研究發現oracle 有個
  • ubuntu上mysql預設安裝使用的字元集是latin1。 1 查看字元集支持 show character set; 2 查看字元集相關變數 show variables like "character_set%"; 3 設置預設字元集 為解決亂碼問題,最簡單的辦法就是修改預設字元集。修改預設字
  • (一)是否存在一個伺服器性能基線? (1)、有了基線才有一個衡量的指標,否則一切計數器都是沒有意思。否則可能我的系統本來就很慢,例如現在公司就有一個OLAP系統,每天就花了三個小時產生一個報表。也不是問題。 (2)、有了基線才能確認我的伺服器確實存在調整的必要了,例如今天發佈新的代碼後,性能明顯偏離
  • 本文目錄列表: 1、SQL Server中的基準日期 2、smalldatetime的日期範圍 3、smalldatetime的日期範圍和無符號2位元組整數的關係 4、總結語 5、參考清單列表 SQL Server中的基準日期 SQL Server 中針對datetime和smalldatetime這
  • 1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...