
测试异步日志发现程序一直卡着,调了半天才发现感觉可能是线程死锁了,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; } 现象:

1 tracker647 OP 一番研究后找到一个解决思路, 首先日志类已经设了一个`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(); } }; ``` |
2 luassuns 2022-10-11 21:57:01 +08:00 @tracker647 应该先 `m_is_async = false;` 再 fclose ,而且应该在置 false 后 join thread 等待线程结束,要不可能会 fputs 到 close 的 handle 里。 |