gdb在线调试多线程程序

之前面腾讯就被问过gdb能否调试正在运行的程序,今天写线程池demo时,恰好遇到程序"卡住了"。

点击查看代码
#include<pthread.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<functional>
#include<queue>
#include<iostream>

typedef std::function<void()> func;

struct Job {
    int id;
    func run_func;
};

struct ThreadPool {
    pthread_t threads[8];  // 8个线程
    pthread_mutex_t m;   // 互斥锁
    pthread_cond_t cond;    // 条件变量
    std::queue<Job*>q;   // 任务队列

    ThreadPool() {
        for(int i = 0;i < 8;i++) {
            pthread_create(&threads[i], NULL, work_func, this);
        }
        pthread_mutex_init(&m, NULL);
        pthread_cond_init(&cond, NULL);
    }

    static void* work_func(void* args) {
        while(1) {
            Job* job = (static_cast<ThreadPool*>(args))->pop();
            job->run_func();
            // printf("thread %d\n", pthread_self());
            delete job;
        }
    }

    Job* pop() {
        // pthread_mutex_lock(&m);
        while(q.empty()) {  // 同时唤醒 或 虚假唤醒
            printf("wait before\n");
            pthread_cond_wait(&cond, &m);   // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
            printf("wait after\n");
        }
        pthread_mutex_lock(&m);
        Job* job = q.front();   // 取出就绪的任务执行
        q.pop();
        pthread_mutex_unlock(&m);  
        return job;
    } 

    void add(Job* job) {
        pthread_mutex_lock(&m);
        q.push(job);   // 添加一个job到任务队列中
        pthread_cond_signal(&cond);  // 这个可以不改,当为了统一,也放到外面
        pthread_mutex_unlock(&m);
    }
};

int main() {
    ThreadPool pool;
    for(int i = 0;i < 10;i++) {
        Job* job = new Job();
        job->id = i;
        job->run_func = [i]() {
            printf("job %d\n", i);
        };
        pool.add(job);
    }

    for(int i = 0;i < 8;i++) {
        pthread_join(pool.threads[i], NULL);
    }
    printf("main end\n");
    return 0;
} 

运行上面的程序时有一定概率卡死,如图:
预期是打印出Job 0...10

gdb调试多线程

  1. 将进程附加到gdb调试器当中,查看是否创建了新线程:gdb attach 主线程ID

看起来主线程是阻塞在__lll_lock_wait ()

  1. 查看栈信息 bt

进一步发现,阻塞在main-> ThreadPool::add-> __GI___pthread_mutex_lock -> _L_lock_909-> __lll_lock_wait,总之,就是获取不到锁

  1. 查看线程:info threads

可见,有8个线程阻塞在__lll_lock_wait,1个阻塞在pthread_cond_wait

  1. 切换线程:thread n (代表第几个线程)

可见和主线程一样,都是获取不到锁,但是主线程是add中,线程池中的线程是pop中

于是,我们知道大概可能的原因是:8号线程获得了锁但在等待条件变量,而其他线程获取不到锁而阻塞,并且不能给条件变量发送信号

那互斥锁被谁占有了呢,只可能是8号线程了

DESCRIPTION
The pthread_cond_wait() function atomically blocks the current thread waiting on the condition variable specified by cond, and releases the mutex specified by mutex. The waiting thread unblocks only after another thread calls pthread_cond_signal(3), or pthread_cond_broadcast(3) with the same condition variable, and the current thread reacquires the lock on mutex.

但是man文档说,pthread_cond_wait会先释放互斥锁的...迷

总之,改成标准写法吧,把pthread_cond_wait放到mutex内,放在了gist 链接

两个非常推荐的参考:
线程的查看以及利用gdb调试多线程
pthread_cond_wait()用法分析

posted @ 2021-12-26 23:25  Rogn  阅读(93)  评论(0编辑  收藏  举报