9. pthread多线程基础.md 7.8 KB

9 pthread多线程基础

9.1 完整概念讲解

线程基本概念

  • 轻量级进程(LWP):线程是CPU调度的基本单位,进程是资源分配的基本单位
  • 共享地址空间:同一进程内的线程共享代码段、数据段、堆空间,但各自拥有独立的栈和寄存器上下文
  • 线程ID vs 进程ID
    • 进程ID(pid_t)在整个系统中唯一
    • 线程ID(pthread_t)在进程内唯一,不同进程中可能相同
    • 获取方式:getpid() vs pthread_self()

为什么需要多线程

  1. 并发执行:多个任务同时推进,提高CPU利用率
  2. 资源共享:线程间共享内存,无需复杂的IPC机制
  3. 响应性:主线程处理UI/交互,子线程执行耗时任务
  4. 经济性:创建/切换开销远小于进程

POSIX线程标准

  • IEEE POSIX 1003.1c标准(1995年)
  • Linux实现:NPTL(Native POSIX Thread Library)
  • 头文件:<pthread.h>
  • 库:libpthread(编译时加-pthread

线程 vs 进程对比

特性 线程 进程
创建开销 小(~10μs) 大(~100μs)
切换开销 小(~1μs) 大(~10μs)
通信方式 共享内存(直接) IPC(管道/共享内存等)
资源占用 共享地址空间 独立地址空间
安全性 低(数据竞争风险) 高(天然隔离)
典型应用 任务并行、IO重叠 服务隔离、安全边界

9.2 核心API/语法

pthread_create - 创建线程

int pthread_create(
    pthread_t *thread,           // 输出:线程ID
    const pthread_attr_t *attr,  // 属性(NULL为默认)
    void *(*start_routine)(void*), // 线程函数
    void *arg                    // 传递给线程函数的参数
);
// 返回:成功返回0,失败返回错误码(不是-1)

pthread_join - 等待线程结束

int pthread_join(
    pthread_t thread,    // 要等待的线程ID
    void **retval        // 输出:线程函数的返回值
);
// 返回:成功返回0
// 阻塞直到目标线程结束

pthread_detach - 分离线程

int pthread_detach(pthread_t thread);
// 分离后线程结束后自动回收资源,不能用pthread_join
// 常用:pthread_detach(pthread_self()) 在线程函数内部自我分离

pthread_exit - 退出线程

void pthread_exit(void *retval);
// 终止当前线程,retval作为返回值
// 注意:不要返回局部变量地址

线程函数原型

void* thread_func(void* arg) {
    // 类型转换使用实际类型
    int *data = (int*)arg;
    // ...
    int *result = malloc(sizeof(int));
    *result = 42;
    return (void*)result;  // 返回堆内存或静态变量
}

9.3 代码示例

基础示例:创建线程并等待

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* thread_func(void* arg) {
    int id = *(int*)arg;
    printf("线程 %d 运行中, TID: %lu\n", id, pthread_self());
    sleep(1);

    int *result = malloc(sizeof(int));
    *result = id * 100;
    return (void*)result;
}

int main() {
    pthread_t t1, t2;
    int id1 = 1, id2 = 2;
    void *ret1, *ret2;

    if (pthread_create(&t1, NULL, thread_func, &id1) != 0) {
        perror("pthread_create");
        return 1;
    }
    if (pthread_create(&t2, NULL, thread_func, &id2) != 0) {
        perror("pthread_create");
        return 1;
    }

    pthread_join(t1, &ret1);
    pthread_join(t2, &ret2);

    printf("线程1返回: %d\n", *(int*)ret1);
    printf("线程2返回: %d\n", *(int*)ret2);

    free(ret1);
    free(ret2);
    return 0;
}

编译命令:

gcc -o pthread_basic pthread_basic.c -pthread
./pthread_basic

自我分离线程示例

#include <stdio.h>
#include <pthread.h>

void* detached_thread(void* arg) {
    printf("分离线程运行中\n");
    sleep(2);
    printf("分离线程结束\n");
    pthread_detach(pthread_self());  // 自我分离
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, detached_thread, NULL);

    // 主线程不需要pthread_join
    sleep(1);
    printf("主线程结束\n");
    return 0;
}

嵌入式场景:任务处理

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// UI线程(主线程)
void ui_task() {
    for (int i = 0; i < 5; i++) {
        printf("[UI] 刷新界面 %d\n", i);
        usleep(500000);
    }
}

// 后台数据处理线程
void* data_task(void* arg) {
    int sensor_id = *(int*)arg;
    for (int i = 0; i < 10; i++) {
        printf("[DATA] 读取传感器%d: %d\n", sensor_id, i);
        usleep(200000);
    }
    return NULL;
}

int main() {
    pthread_t data_tid;
    int sensor = 1;

    // 启动后台数据采集线程
    pthread_create(&data_tid, NULL, data_task, &sensor);

    // 主线程处理UI
    ui_task();

    pthread_join(data_tid, NULL);
    printf("所有任务完成\n");
    return 0;
}

9.4 注意事项与易错点

1. 返回值陷阱

// 错误:返回局部变量地址(栈内存已释放)
void* bad_thread(void* arg) {
    int result = 42;
    return (void*)&result;  // 未定义行为!
}

// 正确:使用堆内存或静态变量
void* good_thread(void* arg) {
    int *result = malloc(sizeof(int));
    *result = 42;
    return (void*)result;  // OK
}

2. 参数传递

// 错误:传递局部变量地址(主线程可能已修改)
int main() {
    int val = 100;
    pthread_create(&tid, NULL, thread_func, &val);
    val = 200;  // 可能在线程读取前被修改
}

// 正确:动态分配或确保生命周期
int *param = malloc(sizeof(int));
*param = 100;
pthread_create(&tid, NULL, thread_func, param);

3. 常见错误码

错误码 含义 常见原因
EAGAIN 资源不足 线程数达到上限
EINVAL 无效参数 属性设置错误
ESRCH 找不到线程 线程已退出
EDEADLK 死锁 自己等待自己

4. 其他注意事项

  • 不要忽略返回值,用-pthread编译确保链接
  • 全局变量需加锁保护(后续讲mutex)
  • pthread_cancel慎用,可能导致资源泄漏
  • 线程栈大小默认8MB,可通过属性调整

9.5 面试要点(3-5个Q&A)

Q1:线程和进程的区别?为什么多线程比多进程更快?

进程是资源分配单位,线程是调度单位。同进程线程共享地址空间,创建/切换无需拷贝页表、刷新TLB,因此开销小10-100倍。通信也更简单,直接读写共享内存。

_*Q2:pthreadcreate的arg参数为什么是void*?如何安全传递复杂数据?*

void*是C的泛型机制,允许传递任意类型。安全做法:传递指针指向堆内存或结构体,确保生命周期覆盖线程执行期。避免传递栈上的局部变量地址。

Q3:pthread_join和pthread_detach的区别?什么时候用哪个?

join:主线程阻塞等待子线程结束,获取返回值,资源手动回收。 detach:子线程结束后自动回收,不能join。 选择:需要结果或控制用join;fire-and-forget任务用detach。

Q4:如果线程函数访问全局变量,会有什么问题?如何解决?

多线程并发访问共享变量导致数据竞争(race condition),结果不可预测。解决:使用互斥锁(pthread_mutex)、读写锁、原子操作或线程局部存储(__thread)。

Q5:嵌入式系统中多线程的典型应用场景?

  • UI线程 + 数据采集线程(实时性)
  • 网络通信线程 + 业务处理线程
  • 多核并行计算(矩阵运算等)
  • Watchdog线程监控主程序