以下是一个 多线程实例代码(模拟生产者-消费者模型),并附上详细的 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)查看线程信息
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)调试死锁
如果程序卡死:
- 按
Ctrl+C 中断 GDB。 - 检查所有线程的调用栈:
1
| (gdb) thread apply all bt
|
- 如果发现多个线程在
pthread_cond_wait 或 pthread_mutex_lock 处阻塞,可能是死锁。
(6)条件变量调试
- 查看条件变量状态:
1
| (gdb) print cond_producer.__data.__futex
|
- 手动触发信号(测试用):
1
| (gdb) call pthread_cond_signal(&cond_producer)
|
4. 常见问题及解决
- 问题:线程不同步
- 现象:
count 值异常(如负数或超过 BUFFER_SIZE)。 - 解决:检查锁和条件变量的使用是否正确,确保所有共享操作都在锁保护内。
- 问题:死锁
- 现象:程序卡住,线程阻塞在
pthread_cond_wait。 - 解决:确认
pthread_cond_signal 被正确调用,且锁的释放顺序一致。
- 问题:GDB 不显示线程
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 # 允许其他线程继续运行
|