Posts gdb 多线程
Post
Cancel

gdb 多线程

以下是一个 多线程实例代码(模拟生产者-消费者模型),并附上详细的 GDB 调试指南,重点演示如何调试多线程竞争、死锁和线程切换问题。

1. 多线程示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

#define BUFFER_SIZE 5

int buffer[BUFFER_SIZE];  // 共享缓冲区
int count = 0;           // 当前缓冲区中的数据量

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      // 互斥锁
pthread_cond_t cond_producer = PTHREAD_COND_INITIALIZER; // 条件变量(生产者)
pthread_cond_t cond_consumer = PTHREAD_COND_INITIALIZER; // 条件变量(消费者)

// 生产者线程函数
void *producer(void *arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);
        while (count == BUFFER_SIZE) {  // 缓冲区满,等待
            printf("Producer waiting...\n");
            pthread_cond_wait(&cond_producer, &mutex);
        }
        buffer[count++] = i;  // 生产数据
        printf("Produced: %d (count=%d)\n", i, count);
        pthread_cond_signal(&cond_consumer);  // 唤醒消费者
        pthread_mutex_unlock(&mutex);
        sleep(1);  // 模拟生产耗时
    }
    return NULL;
}

// 消费者线程函数
void *consumer(void *arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);
        while (count == 0) {  // 缓冲区空,等待
            printf("Consumer waiting...\n");
            pthread_cond_wait(&cond_consumer, &mutex);
        }
        int data = buffer[--count];  // 消费数据
        printf("Consumed: %d (count=%d)\n", data, count);
        pthread_cond_signal(&cond_producer);  // 唤醒生产者
        pthread_mutex_unlock(&mutex);
        sleep(2);  // 模拟消费耗时
    }
    return NULL;
}

int main() {
    pthread_t tid_producer, tid_consumer;

    // 创建生产者和消费者线程
    pthread_create(&tid_producer, NULL, producer, NULL);
    pthread_create(&tid_consumer, NULL, consumer, NULL);

    // 等待线程结束
    pthread_join(tid_producer, NULL);
    pthread_join(tid_consumer, NULL);

    return 0;
}

2. 编译并运行

1
2
gcc -g -pthread producer_consumer.c -o pc
./pc

预期输出:交替打印生产者和消费者的操作日志。

3. GDB 多线程调试指南

(1)启动 GDB 并设置断点

1
2
3
4
5
6
gdb ./pc
(gdb) break producer   # 在生产者函数入口设断点
(gdb) break consumer   # 在消费者函数入口设断点
(gdb) break 20        # 在生产者等待条件变量的行设断点
(gdb) break 35        # 在消费者等待条件变量的行设断点
(gdb) run

(2)查看线程信息

1
(gdb) info threads
Id Target Id Frame 
1 Thread 0x7ffff7da2740 (LWP 1234) main () at pc.c:40
2 Thread 0x7ffff7da1700 (LWP 1235) producer (arg=0x0) at pc.c:15
3 Thread 0x7ffff75a0700 (LWP 1236) consumer (arg=0x0) at pc.c:30

(3)切换线程并单步执行

1
2
3
4
5
(gdb) thread 2          # 切换到生产者线程
(gdb) next              # 单步执行(跳过函数调用)
(gdb) print count       # 查看共享变量
(gdb) thread 3          # 切换到消费者线程
(gdb) next

(4)调试线程竞争

  • 监视共享变量 count
    1
    
    (gdb) watch count     # 当count被修改时中断
    
  • 检查锁状态
    1
    
    (gdb) print mutex.__data.__lock  # 查看互斥锁状态(0=未锁定,1=锁定)
    

(5)调试死锁

如果程序卡死:

  1. Ctrl+C 中断 GDB。
  2. 检查所有线程的调用栈:
    1
    
    (gdb) thread apply all bt
    
    • 如果发现多个线程在 pthread_cond_waitpthread_mutex_lock 处阻塞,可能是死锁。

(6)条件变量调试

  • 查看条件变量状态
    1
    
    (gdb) print cond_producer.__data.__futex
    
  • 手动触发信号(测试用)
    1
    
    (gdb) call pthread_cond_signal(&cond_producer)
    

4. 常见问题及解决

  1. 问题:线程不同步
    • 现象count 值异常(如负数或超过 BUFFER_SIZE)。
    • 解决:检查锁和条件变量的使用是否正确,确保所有共享操作都在锁保护内。
  2. 问题:死锁
    • 现象:程序卡住,线程阻塞在 pthread_cond_wait
    • 解决:确认 pthread_cond_signal 被正确调用,且锁的释放顺序一致。
  3. 问题:GDB 不显示线程
    • 解决:编译时务必加上 -pthread 选项。

5. 高级调试技巧

  • 记录线程切换历史
    1
    2
    3
    
    (gdb) set scheduler-locking off
    (gdb) set logging on
    (gdb) thread apply all bt full  # 记录所有线程的完整堆栈
    
  • 非阻塞调试
    1
    
    (gdb) set non-stop on  # 允许其他线程继续运行
    
This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags