复习——线程(pthread)

线程(pthread)知识点整理

1. 线程概念与特点

线程 vs 进程

特征 进程 线程
资源分配 最小资源分配单位 最小执行单位
资源共享 私有资源空间 共享进程资源,部分私有
通信方式 IPC(复杂) 直接通信(简单)
创建开销 小(轻量级进程)
稳定性 相对较低
效率 相对较低 高(节省30%资源)

线程优点

  1. 资源共享:共享进程内的全局变量、堆区、文件描述符等

  2. 高效:创建销毁开销小,上下文切换快

  3. 并发性好:适合I/O密集型任务

  4. 简化通信:直接共享内存,无需复杂IPC

线程缺点

  1. 稳定性差:一个线程崩溃可能影响整个进程

  2. 调试复杂:gdb调试需要特殊命令

  3. 缺乏保护:线程间没有内存保护

2. POSIX线程编程框架

三部曲:创建线程 → 线程执行 → 资源回收

编译与头文件

复制代码
#include <pthread.h>  // 线程头文件
gcc -g -pthread source.c  // 编译命令(-lpthread)

永久设置编译别名

复制代码
# 编辑 ~/.bashrc
alias gcc='gcc -g -pthread'
# 生效配置
source ~/.bashrc

3. 线程创建与管理

3.1 创建线程

复制代码
int pthread_create(
    pthread_t *thread,              // 线程ID(出参)
    const pthread_attr_t *attr,     // 线程属性(NULL为默认)
    void *(*start_routine)(void *), // 线程回调函数
    void *arg                       // 回调函数参数
);

重要特性

  • 一次只能创建一个线程

  • 主线程退出,所有子线程也退出

  • 线程ID是CPU维护的唯一标识

  • 多个线程可执行同一回调函数

3.2 获取线程ID

复制代码
pthread_t pthread_self(void);  // 获取当前线程ID

3.3 查看线程信息命令

复制代码
ps -eLf                  # 查看所有线程
ps -eLo pid,ppid,lwp,stat,comm  # 查看LWP(轻量级进程)
pstree                   # 查看线程树结构

4. 线程退出与回收

4.1 线程退出方式

复制代码
// 1. 自行退出(自杀)
void pthread_exit(void *retval);  // retval: 退出状态

// 2. 强制退出(他杀)
int pthread_cancel(pthread_t thread);

4.2 线程资源回收

复制代码
int pthread_join(pthread_t thread, void **retval);

回收策略

  1. 有限时间结束 → pthread_join阻塞等待

  2. 可能休眠阻塞 → 超时后强制回收

  3. 长期运行 → 不回收(可能导致资源泄漏)

4.3 分离属性

复制代码
// 方法1:设置属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, func, NULL);
pthread_attr_destroy(&attr);

// 方法2:直接分离
int pthread_detach(pthread_t thread);

5. 线程参数传递

5.1 传递整型参数

复制代码
// 主线程
int value = 100;
pthread_create(&tid, NULL, func, (void *)(long)value);

// 子线程
void *func(void *arg) {
    int val = (int)(long)arg;
    // 使用val
    return NULL;
}

5.2 传递字符串参数

复制代码
// 栈区字符数组(危险:线程结束后可能失效)
char str[] = "hello";
pthread_create(&tid, NULL, func, str);

// 堆区字符串(安全)
char *str = malloc(128);
strcpy(str, "hello");
pthread_create(&tid, NULL, func, str);
// 线程结束后需要free

5.3 传递结构体参数

复制代码
typedef struct {
    int id;
    char name[32];
    float score;
} Student;

// 主线程
Student stu = {1, "Tom", 90.5};
pthread_create(&tid, NULL, func, &stu);

// 子线程
void *func(void *arg) {
    Student *stu = (Student *)arg;
    printf("ID:%d, Name:%s, Score:%.1f\n", 
           stu->id, stu->name, stu->score);
    return NULL;
}

5.4 计算器示例

复制代码
typedef struct {
    float a;
    float b;
    char op;  // + - * /
    float result;
} Calculator;

void *calc_func(void *arg) {
    Calculator *calc = (Calculator *)arg;
    switch(calc->op) {
        case '+': calc->result = calc->a + calc->b; break;
        case '-': calc->result = calc->a - calc->b; break;
        case '*': calc->result = calc->a * calc->b; break;
        case '/': 
            if(calc->b != 0) calc->result = calc->a / calc->b;
            else calc->result = 0;
            break;
    }
    return NULL;
}

6. 线程返回值

返回值传递原理

  • pthread_exit(void *retval) → 返回地址

  • pthread_join(tid, void **retval) → 接收地址

有效返回地址类型:

  1. 全局变量(可直接访问,意义不大)

  2. 静态变量(生命周期长)

  3. 堆区变量(推荐:可控制生命周期)

复制代码
// 示例:返回堆区字符串
void *thread_func(void *arg) {
    char *result = malloc(128);
    strcpy(result, "Thread completed successfully");
    pthread_exit(result);
}

int main() {
    pthread_t tid;
    char *retval;
    
    pthread_create(&tid, NULL, thread_func, NULL);
    pthread_join(tid, (void **)&retval);
    
    printf("Thread returned: %s\n", retval);
    free(retval);  // 必须释放
    return 0;
}

7. 线程清理函数

复制代码
void cleanup_func(void *arg) {
    printf("Cleanup: %s\n", (char *)arg);
}

void *thread_func(void *arg) {
    // 注册清理函数
    pthread_cleanup_push(cleanup_func, "Resource cleanup");
    
    // 线程工作代码
    // do something...
    
    // 执行清理函数(参数为0则不执行)
    pthread_cleanup_pop(1);  // 1:执行, 0:不执行
    return NULL;
}

8. 线程互斥(Mutex)

互斥锁框架

复制代码
// 1. 定义互斥锁
pthread_mutex_t mutex;

// 2. 初始化
pthread_mutex_init(&mutex, NULL);

// 3. 加锁
pthread_mutex_lock(&mutex);
// 临界区代码
pthread_mutex_unlock(&mutex);  // 4. 解锁

// 5. 销毁
pthread_mutex_destroy(&mutex);

非阻塞加锁

复制代码
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 成功: 0, 失败: EBUSY(锁已被占用)

互斥锁示例:共享缓冲区

复制代码
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

char buffer[1024];
pthread_mutex_t mutex;

void *writer_thread(void *arg) {
    char *msg = (char *)arg;
    for(int i = 0; i < 5; i++) {
        pthread_mutex_lock(&mutex);
        strcpy(buffer, msg);
        printf("%s wrote: %s\n", msg, buffer);
        pthread_mutex_unlock(&mutex);
        usleep(100000);  // 100ms
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    
    pthread_mutex_init(&mutex, NULL);
    
    pthread_create(&tid1, NULL, writer_thread, "Thread1");
    pthread_create(&tid2, NULL, writer_thread, "Thread2");
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    pthread_mutex_destroy(&mutex);
    return 0;
}

9. 线程同步(信号量)

信号量框架

复制代码
#include <semaphore.h>

// 1. 定义信号量
sem_t sem;

// 2. 初始化(二值信号量)
sem_init(&sem, 0, 1);  // 参数:信号量,pshared(0=线程), 初始值

// 3. P操作(申请资源)
sem_wait(&sem);
// 临界区
sem_post(&sem);  // 4. V操作(释放资源)

// 5. 销毁
sem_destroy(&sem);

同步示例:生产者-消费者

复制代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>

char buffer[256];
sem_t sem_empty;  // 缓冲区空信号量
sem_t sem_full;   // 缓冲区满信号量

void *input_thread(void *arg) {
    while(1) {
        sem_wait(&sem_empty);  // 等待缓冲区空
        printf("Enter message: ");
        fgets(buffer, sizeof(buffer), stdin);
        buffer[strlen(buffer)-1] = '\0';  // 去掉换行符
        
        if(strcmp(buffer, "quit") == 0) {
            sem_post(&sem_full);
            break;
        }
        sem_post(&sem_full);  // 通知处理线程
    }
    return NULL;
}

void *process_thread(void *arg) {
    while(1) {
        sem_wait(&sem_full);  // 等待数据
        if(strcmp(buffer, "quit") == 0) {
            sem_post(&sem_empty);
            break;
        }
        printf("Processed: Length=%lu\n", strlen(buffer));
        sem_post(&sem_empty);  // 通知可继续输入
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    
    sem_init(&sem_empty, 0, 1);  // 初始缓冲区为空
    sem_init(&sem_full, 0, 0);   // 初始无数据
    
    pthread_create(&tid1, NULL, input_thread, NULL);
    pthread_create(&tid2, NULL, process_thread, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    sem_destroy(&sem_empty);
    sem_destroy(&sem_full);
    
    return 0;
}

10. 综合示例:火车票售票系统

复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

#define TICKET_COUNT 100

int tickets = TICKET_COUNT;
pthread_mutex_t mutex;
int window1_count = 0;
int window2_count = 0;

void *sell_tickets(void *arg) {
    char *window_name = (char *)arg;
    
    while(1) {
        pthread_mutex_lock(&mutex);
        
        if(tickets > 0) {
            printf("%s 卖出车票 %d\n", window_name, TICKET_COUNT - tickets + 1);
            tickets--;
            
            if(strcmp(window_name, "窗口1") == 0)
                window1_count++;
            else
                window2_count++;
                
            pthread_mutex_unlock(&mutex);
            usleep(100000);  // 模拟卖票时间
        } else {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    
    pthread_mutex_init(&mutex, NULL);
    
    pthread_create(&tid1, NULL, sell_tickets, "窗口1");
    pthread_create(&tid2, NULL, sell_tickets, "窗口2");
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    printf("\n售票统计:\n");
    printf("窗口1卖出: %d张\n", window1_count);
    printf("窗口2卖出: %d张\n", window2_count);
    printf("总共卖出: %d张\n", TICKET_COUNT - tickets);
    
    pthread_mutex_destroy(&mutex);
    return 0;
}

11. 线程控制函数对比

进程操作 线程操作 功能
fork() pthread_create() 创建
getpid() pthread_self() 获取ID
exit() pthread_exit() 退出
wait() pthread_join() 等待回收
kill() pthread_cancel() 强制终止
atexit() pthread_cleanup_push/pop() 清理函数

12. 死锁与预防

死锁产生条件

  1. 互斥条件:资源只能被一个线程使用

  2. 请求与保持:线程持有资源并请求新资源

  3. 不可剥夺:资源只能由持有者释放

  4. 循环等待:线程间形成等待环

预防策略

  1. 按顺序申请资源:所有线程按相同顺序申请锁

  2. 使用超时机制pthread_mutex_trylock + 超时重试

  3. 避免嵌套锁:尽量减少锁的嵌套层次

  4. 使用资源层级:为资源分配优先级

避免死锁示例

复制代码
// 按固定顺序获取锁
void safe_operation(pthread_mutex_t *lock1, pthread_mutex_t *lock2) {
    // 总是先获取lock1,再获取lock2
    pthread_mutex_lock(lock1);
    pthread_mutex_lock(lock2);
    
    // 临界区操作
    
    pthread_mutex_unlock(lock2);
    pthread_mutex_unlock(lock1);
}

13. 线程调度与让出CPU

复制代码
pthread_yield();   // 主动让出CPU(建议使用)
usleep(1000);      // 休眠方式让出CPU

14. 综合练习:餐厅管理系统

复制代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define TABLE_COUNT 3

typedef struct {
    char name[100];
    int total_num;
    int call_num;
    pthread_mutex_t lock;
} Restaurant;

Restaurant rest;
sem_t tables;  // 餐桌信号量

void *customer(void *arg) {
    int id = *(int *)arg;
    printf("顾客%d [%s] 正在用餐...\n", id, rest.name);
    sleep(rand() % 3 + 1);  // 用餐时间
    printf("顾客%d [%s] 离开\n", id, rest.name);
    sem_post(&tables);  // 释放餐桌
    free(arg);
    return NULL;
}

void *waiter(void *arg) {
    while(1) {
        pthread_mutex_lock(&rest.lock);
        if(rest.call_num < rest.total_num) {
            sem_wait(&tables);  // 等待空桌
            int *id = malloc(sizeof(int));
            *id = ++rest.call_num;
            pthread_t tid;
            pthread_create(&tid, NULL, customer, id);
            pthread_detach(tid);
            printf("服务员叫号: %d [%s]\n", rest.call_num, rest.name);
        } else {
            pthread_mutex_unlock(&rest.lock);
            break;
        }
        pthread_mutex_unlock(&rest.lock);
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t waiter_tid;
    
    // 初始化
    sem_init(&tables, 0, TABLE_COUNT);
    pthread_mutex_init(&rest.lock, NULL);
    rest.total_num = 0;
    rest.call_num = 0;
    
    // 输入顾客信息
    printf("请输入顾客姓名: ");
    fgets(rest.name, sizeof(rest.name), stdin);
    rest.name[strlen(rest.name)-1] = '\0';
    
    printf("请输入顾客总数: ");
    scanf("%d", &rest.total_num);
    getchar();  // 清除缓冲区
    
    // 启动服务员线程
    pthread_create(&waiter_tid, NULL, waiter, NULL);
    pthread_join(waiter_tid, NULL);
    
    // 清理
    sem_destroy(&tables);
    pthread_mutex_destroy(&rest.lock);
    
    printf("所有顾客已服务完毕!\n");
    return 0;
}

15. 线程调试技巧

GDB调试线程

复制代码
gdb ./a.out
(gdb) run
(gdb) info threads      # 查看所有线程
(gdb) thread 2          # 切换到线程2
(gdb) bt                # 查看线程调用栈
(gdb) thread apply all bt  # 查看所有线程调用栈

调试建议

  1. 编译带调试信息gcc -g -pthread

  2. 使用线程局部变量:避免全局变量冲突

  3. 添加调试输出:打印线程ID和状态

  4. 逐步测试:先测试单线程,再测试多线程

16.总结

多线程编程的关键在于正确处理共享资源同步互斥问题。合理使用互斥锁和信号量可以避免竞态条件和死锁,同时需要注意线程的生命周期管理和资源回收。在实际开发中,应根据具体需求选择合适的线程模型和同步机制。

相关推荐
极地星光2 小时前
软件发布中.symbols文件夹单独发布全指南:从需求解析到自动化落地
运维·自动化
凯子坚持 c2 小时前
在家搭个私人云音乐库?用 Docker+cpolar 随时随地听歌
运维·docker·容器
!chen2 小时前
让镜像构建更轻量,告别 Docker 依赖
运维·docker·容器
alanesnape2 小时前
Java异常处理详解:Exception、ArithmeticException、FileNotFoundException
java·开发语言
while(1){yan}2 小时前
数据链路层与物理层
java·网络·网络协议
aml258__2 小时前
一、Cisco( OSPF多区域与路由汇总技术实践:ABR优化网络路由表实验)251220
运维·网络·动态路由协议·网络优化·ospf多区域·abr·路由汇总
2503_930123932 小时前
Docker全阶段详解
运维·docker·容器
papaofdoudou2 小时前
基于QEMU 模拟intel-iommu的sva/svm demo环境搭建和验证
算法·机器学习·支持向量机
再__努力1点2 小时前
【78】HOG+SVM行人检测实践指南:从算法原理到python实现
开发语言·人工智能·python·算法·机器学习·支持向量机·计算机视觉