WebServer 异步日志模块, Log 类构析导致线程死锁, 不知如何解除异步线程阻塞 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tracker647
V2EX    C++

WebServer 异步日志模块, Log 类构析导致线程死锁, 不知如何解除异步线程阻塞

  •  
  •   tracker647 2022-09-30 20:07:52 +08:00 1857 次点击
    这是一个创建于 1172 天前的主题,其中的信息可能已经有所发展或是发生改变。

    测试异步日志发现程序一直卡着,调了半天才发现感觉可能是线程死锁了,gdb 查了两个线程 bt 果然是,主线程调用delete m_queue时要构析条件变量, 然而另一个线程已经用条件变量 wait 拿到互斥锁阻塞自己没法退出,导致主线程得一直等待。

    把~Log 的delete m_queue一行删除就解决了,但想尝试做到既能内存 0 泄露又能正常退出程序, 问题是不知道怎么解决工作线程的 wait 状态。

    一开始以为在~block_queue 部分加入m_cond.broadcast()就能解除工作线程 wait 状态了,毕竟是主程序调用的~block_queue, 然而不知道为何仍旧死锁。

    热心的群友们有何良方吗?

    log.h

    #ifndef LOG_H #define LOG_H #include<iostream> #include<string> #include<cstring> #include<stdarg.h> #include<time.h> #include<sys/time.h> #include<queue> #include<assert.h> #include "../lock/locker.h" #include "../log/block_queue.h" using namespace std; class Log{ public: Log(); ~Log(); void init(const char *file_path, int log_buf_size = 8192, int max_line = 80000, int queue_size = 800); static Log* get_instance(){ static Log instance; return &instance; } static void* async_write_thread(void *args){ Log::get_instance()->async_write_log(); return 0; }; void write_log(int level, const char *format,...); void flush() { m_mutex.lock(); fflush(m_fp); m_mutex.unlock(); }; private: char *get_current_time(char *time_buf, const char *format); void async_write_log(){ string logstr; while(m_is_async && m_block_queue->pop(logstr)){ m_mutex.lock(); if(DEBUG) printf("log puts\n"); fputs(logstr.c_str(),m_fp); m_mutex.unlock(); } if(DEBUG) printf("thread end\n"); }; private: char m_log_dir[128]; //日志存放目录 char m_log_name[128]; //日志名 FILE *m_fp; bool m_is_async; //异步日志模式,控制异步线程 int m_today; int m_max_line; int m_line_cnt; int m_wlog_buf_size; char *m_wlog_buf; locker m_mutex; block_queue<string> *m_block_queue; }; #define LOG_DEBUG(format, ...) {Log::get_instance()->write_log(0,format,##__VA_ARGS__); Log::get_instance()->flush();} #define LOG_INFO(format,...) {Log::get_instance()->write_log(1,format,##__VA_ARGS__); Log::get_instance()->flush();} #define LOG_WARN(format, ...) {Log::get_instance()->write_log(2,format,##__VA_ARGS__); Log::get_instance()->flush();} #define LOG_ERROR(format, ...) {Log::get_instance()->write_log(3,format,##__VA_ARGS__); Log::get_instance()->flush();} #endif //LOG_H 

    locker.h

    #ifndef LOCKER_H #define LOCKER_H #include<exception> #include<pthread.h> #include<semaphore.h> class sem{ public: sem(){ if(sem_init(&m_sem, 0, 0) != 0){ throw std::exception(); } } bool wait(){ return sem_wait(&m_sem) == 0; } bool post(){ return sem_post(&m_sem) == 0; } ~sem(){ sem_destroy(&m_sem); } private: sem_t m_sem; }; class locker{ public: locker(){ if(0 != pthread_mutex_init(&m_mutex, NULL)){ throw std::exception(); } } pthread_mutex_t *get(){ return &m_mutex; } bool lock(){ return pthread_mutex_lock(&m_mutex) == 0; } bool unlock(){ return pthread_mutex_unlock(&m_mutex) == 0; } ~locker(){ pthread_mutex_destroy(&m_mutex); } private: pthread_mutex_t m_mutex; }; class cond{ public: cond(){ if(0 != pthread_cond_init(&m_cond,NULL)){ throw std::exception(); } } ~cond(){ pthread_cond_destroy(&m_cond); } bool wait(pthread_mutex_t *m_mutex){ int ret = pthread_cond_wait(&m_cond,m_mutex); return ret == 0; } bool signal(){ int ret = pthread_cond_signal(&m_cond); return ret == 0; } bool broadcast(){ int ret = pthread_cond_broadcast(&m_cond); return ret == 0; } private: pthread_cond_t m_cond; }; #endif 

    block_queue.h

    #ifndef BLOCK_QUEUE #define BLOCK_QUEUE #include<queue> #include "../lock/locker.h" template <typename T> class block_queue{ public: block_queue(int sz):m_maxsize(sz){} //往阻塞队列加入日志 string bool push(const T &items){ m_mutex.lock(); //如果队列已满,需通知消费者 if(m_queue.size() >= m_maxsize){ m_cond.broadcast(); m_mutex.unlock(); return false; } m_queue.push(items); m_cond.broadcast(); m_mutex.unlock(); return true; } bool pop(T &items){ m_mutex.lock(); //若队列为空,需等待条件变量 while(m_queue.size() <= 0){ if(!m_cond.wait(m_mutex.get())){ m_mutex.unlock(); return false; } } items = m_queue.front(); m_queue.pop(); m_mutex.unlock(); return true; } bool full(){ m_mutex.lock(); if (m_queue.size() >= m_maxsize){ m_mutex.unlock(); return true; } m_mutex.unlock(); return false; } bool empty(){ m_mutex.lock(); if(m_queue.size() == 0){ m_mutex.unlock(); return true; } m_mutex.unlock(); return false; } ~block_queue(){ m_cond.broadcast(); } private: int m_maxsize; locker m_mutex; cond m_cond; std::queue<T> m_queue; }; #endif 

    testlog.cpp

    #include"log.h" #include<unistd.h> int main(){ Log::get_instance()->init("testfile"); LOG_INFO("This is a test message:)"); sleep(3); const char *mes = "log ends"; write(1,mes,strlen(mes)); printf("log ends\n"); return 0; } 

    现象:

    image-20220930192323938

    2 条回复    2022-10-11 21:57:01 +08:00
    tracker647
        1
    tracker647  
    OP
       2022-09-30 20:3:10 +08:00
    一番研究后找到一个解决思路, 首先日志类已经设了一个`m_is_async`的 bool 变量用于打破工作线程的循环,而 pop 条件变量的解锁条件是阻塞队列内有 push 东西经历,于是在`~Log`放了一行操作让`m_queue`push 东西打破工作线程的阻塞状态退出,工作线程再次 while 检查就会发现`m_is_async`已经被设为 false 了,从而正常退出。

    ```
    //Log 类
    Log::Log(){
    m_line_cnt = 0;
    m_is_async = true;
    memset(m_log_dir,0,sizeof(m_log_dir));
    memset(m_log_name,0,sizeof(m_log_name));
    }

    Log::~Log(){
    if (m_fp != NULL){
    fclose(m_fp);
    }
    m_is_async = false;
    delete[] m_wlog_buf;
    m_block_queue->push("log end"); //用于结束日志线程的阻塞状态,不加会死锁
    delete m_block_queue;

    }

    void async_write_log(){
    string logstr;
    while(m_is_async && m_block_queue->pop(logstr)){
    m_mutex.lock();
    fputs(logstr.c_str(),m_fp);
    m_mutex.unlock();
    }
    };
    ```
    luassuns
        2
    luassuns  
       2022-10-11 21:57:01 +08:00
    @tracker647 应该先 `m_is_async = false;` 再 fclose ,而且应该在置 false 后 join thread 等待线程结束,要不可能会 fputs 到 close 的 handle 里。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3845 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 38ms UTC 10:16 PVG 18:16 LAX 02:16 JFK 05:16
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86