Linux线程概念与控制(二):POSIX线程库API实战

💬 承接上文:在上一篇中,我们深入理解了线程的本质、虚拟地址空间的分页机制以及线程与进程的区别。理论基础已经打牢,现在是时候动手实践了!本篇将带你掌握Linux下多线程编程的核心API,从线程创建到终止、等待、分离,每个API都配合完整可运行的代码示例,让你真正学会多线程编程!

👍 学习目标:掌握POSIX线程库的使用、理解pthread_t的本质、学会创建和控制线程、理解LWP与线程ID的区别、掌握线程的三种终止方式、理解线程等待和分离的概念。

🚀 实战为主:本篇以代码实战为主,每个知识点都有完整示例,建议边看边敲代码!


一、POSIX线程库入门

1.1 什么是POSIX线程库

POSIX线程(pthread):
- POSIX: Portable Operating System Interface(可移植操作系统接口)
- pthread: POSIX thread的缩写
- 是一套跨平台的线程API标准

特点:
✓ 跨平台:Linux、Unix、MacOS都支持
✓ 标准化:遵循POSIX标准
✓ 功能完善:涵盖线程创建、同步、销毁等全流程

在Linux下,与线程相关的函数构成了一个完整的系列,绝大多数函数名都以pthread_开头,这让API非常容易识别和记忆。


1.2 如何使用pthread库

使用pthread库需要链接pthread库

# 错误的编译方式(会报undefined reference错误)
gcc test.c -o test

# 正确的编译方式(需要-lpthread选项)
gcc test.c -o test -lpthread

# 说明:
# -l 表示链接库
# pthread 是库的名称
# 实际链接的库文件是 libpthread.so

步骤3:编写线程代码

// 这是一个最简单的多线程程序框架
#include <stdio.h>
#include <pthread.h>

void* thread_func(void* arg) {
    printf("子线程运行中\n");
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);
    pthread_join(tid, NULL);
    printf("主线程结束\n");
    return 0;
}

1.3 pthread的错误处理机制

pthread库的错误处理与传统函数不同,这点需要特别注意:

传统函数的错误处理:

// 传统POSIX函数(如open、read等)
int fd = open("file.txt", O_RDONLY);
if (fd == -1) {
    // 失败返回-1
    perror("open");  // 通过全局变量errno获取错误信息
}

pthread函数的错误处理:

// pthread函数
int ret = pthread_create(&tid, NULL, thread_func, NULL);
if (ret != 0) {
    // 不设置全局变量errno
    fprintf(stderr, "pthread_create: %s\n", strerror(ret));
}

📌 核心区别:

传统函数:
- 成功返回0或正值
- 失败返回-1
- 设置全局变量errno

pthread函数:
- 成功返回0  
- 失败返回错误码(正整数)
- 不设置errno(避免多线程冲突)
- 通过返回值判断错误

原因:
- errno是全局变量,多线程共享会冲突
- pthread提供了线程内的errno副本
- 但推荐直接使用返回值判断错误(开销更小)

正确的错误检查示例:

#include <stdio.h>
#include <string.h>   // strerror
#include <pthread.h>

void* thread_func(void* arg) {
    return NULL;
}

int main() {
    pthread_t tid;
    int ret;
    
    // 正确的错误检查方式
    ret = pthread_create(&tid, NULL, thread_func, NULL);
    if (ret != 0) {
        fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
        return 1;
    }
    
    pthread_join(tid, NULL);
    return 0;
}

二、线程创建pthread_create

2.1 函数原型详解

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr,
                   void *(*start_routine)(void*), 
                   void *arg);

参数说明:

参数1: pthread_t *thread
- 输出参数,用于返回新线程的ID
- pthread_t是一个不透明类型(实现相关)
- 在Linux NPTL实现中,pthread_t是unsigned long类型

参数2: const pthread_attr_t *attr  
- 线程属性,设置线程的各种特性
- 传NULL表示使用默认属性
- 默认属性:joinable(可join)、非分离、默认栈大小等

参数3: void *(*start_routine)(void*)函数指针,指向线程要执行的函数
- 函数签名必须是: void* func(void*)
- 参数和返回值都是void*,可以传递任意类型指针

参数4: void *arg
- 传递给start_routine的参数
- 可以传递任意类型的指针
- 如果不需要参数,传NULL

返回值:
- 成功返回0
- 失败返回错误码(正整数)

2.2 第一个多线程程序

让我们写一个完整的多线程程序,创建一个新线程,让它和主线程交替打印信息:

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

// 子线程要执行的函数
void* thread_routine(void* arg) {
    int i;
    for (i = 0; i < 5; i++) {
        printf("I am thread 1\n");
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid;
    int ret;
    
    // 创建新线程
    ret = pthread_create(&tid, NULL, thread_routine, NULL);
    if (ret != 0) {
        fprintf(stderr, "pthread_create: %s\n", strerror(ret));
        return 1;
    }
    
    // 主线程继续执行
    int i;
    for (i = 0; i < 5; i++) {
        printf("I am main thread\n");
        sleep(1);
    }
    
    return 0;
}

编译运行:

I am main thread
I am thread 1
I am main thread
I am thread 1
I am main thread
I am thread 1
I am main thread
I am thread 1
I am main thread
  1. 主线程和子线程交替打印(顺序可能不同)
  2. 两个线程并发执行,互不阻塞
  3. 主线程结束后,进程就终止了(子线程也被强制终止)

问题: 为什么主线程结束,子线程也结束了?
答: 主线程return相当于调用exit(),会终止整个进程
解决: 使用pthread_join等待子线程(后面讲解)


2.3 pthread_self获取线程ID

每个线程都可以通过pthread_self获取自己的线程ID:

pthread_t pthread_self(void);

返回值:

返回调用线程的线程ID(pthread_t类型)

示例代码:

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

void* thread_routine(void* arg) {
    printf("子线程ID: %lu\n", pthread_self());
    sleep(1);
    return NULL;
}

int main() {
    pthread_t tid;
    
    pthread_create(&tid, NULL, thread_routine, NULL);
    
    printf("主线程ID: %lu\n", pthread_self());
    printf("通过tid获取的子线程ID: %lu\n", tid);
    
    sleep(2);  // 等待子线程打印
    return 0;
}

运行结果:

$ ./thread2
主线程ID: 140248607516480
通过tid获取的子线程ID: 140248607512320
子线程ID: 140248607512320

📌 理解pthread_t:

问题: pthread_t到底是什么?

答案: pthread_t的实现由POSIX库决定
- 在Linux NPTL实现中,pthread_t是unsigned long类型
- 它的值是一个虚拟地址
- 这个地址指向线程控制块(TCB)的位置
- TCB存储了线程的各种信息(栈地址、寄存器、优先级等)

重要: pthread_t是进程级唯一的,不是系统级唯一的!
- 不同进程可能有相同的pthread_t值
- 但在同一个进程内,pthread_t是唯一的

2.4 LWP:真正的线程ID

2.4.1 pthread_t vs LWP
pthread_t:
- pthread库维护的线程ID
- 作用域:进程级
- 类型:unsigned long(Linux NPTL)
- 本质:线程控制块的虚拟地址
- 用途:pthread库的API参数

LWP (Light Weight Process):
- 内核维护的轻量级进程ID
- 作用域:系统级(全局唯一)
- 类型:pid_t(整数)
- 本质:内核调度的实体
- 用途:内核调度、ps命令查看
2.4.2 查看LWP

我们可以通过ps命令查看线程的LWP:

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

void* thread_routine(void* arg) {
    while (1) {
        printf("子线程运行中, pthread_t = %lu\n", pthread_self());
        sleep(2);
    }
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_routine, NULL);
    
    while (1) {
        printf("主线程运行中, pthread_t = %lu\n", pthread_self());
        sleep(2);
    }
    
    return 0;
}

运行程序,然后查看线程信息:

# 运行程序
$ ./thread3 &
[1] 12345

# 查看线程信息(-L选项显示线程)
$ ps -aL | head -1 && ps -aL | grep thread3
  PID   LWP TTY          TIME CMD
12345 12345 pts/0    00:00:00 thread3    ← 主线程
12345 12346 pts/0    00:00:00 thread3    ← 子线程

# 说明:
# PID: 进程ID(相同,都是12345)
# LWP: 轻量级进程ID(不同,12345和12346)
# 主线程的LWP等于PID
# 子线程的LWP由内核分配

📌 核心理解:

1. Linux下线程就是轻量级进程
2. 内核不区分线程和进程,都用task_struct表示
3. 主线程的LWP == PID
4. 子线程的LWP != PID,但属于同一个进程组

用图示理解:
进程12345
├─ LWP 12345 (主线程, pthread_t=0x7f1234567000)
└─ LWP 12346 (子线程, pthread_t=0x7f1234563000)

内核眼中: 两个task_struct,都可以独立调度
pthread库眼中: 同一个进程的两个线程,共享地址空间

2.5 线程参数传递

2.5.1 传递单个参数
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread_routine(void* arg) {
    int num = *(int*)arg;  // 转换void*为int*,再解引用
    printf("子线程收到参数: %d\n", num);
    return NULL;
}

int main() {
    pthread_t tid;
    int data = 100;
    
    // 传递data的地址
    pthread_create(&tid, NULL, thread_routine, &data);
    
    sleep(1);  // 等待子线程执行
    return 0;
}

⚠️ 危险示例(不要这样做):

void* thread_routine(void* arg) {
    int num = *(int*)arg;
    printf("num = %d\n", num);
    return NULL;
}

int main() {
    pthread_t tid;
    int i;
    for (i = 0; i < 5; i++) {
        // 错误! 传递局部变量i的地址
        pthread_create(&tid, NULL, thread_routine, &i);
    }
    sleep(1);
    return 0;
}

// 问题: 所有线程共享变量i的地址
// 当循环执行时,i的值会变化
// 可能所有线程都打印5

// 解决方法1: 为每个线程分配独立的参数
// 解决方法2: 直接传值(把整数转为指针)
2.5.2 传递多个参数
#include <stdio.h>
#include <pthread.h>
#include <string.h>

// 定义参数结构体
struct thread_arg {
    int id;
    char name[32];
};

void* thread_routine(void* arg) {
    struct thread_arg* param = (struct thread_arg*)arg;
    printf("线程%d: %s\n", param->id, param->name);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    struct thread_arg arg1 = {1, "Thread-A"};
    struct thread_arg arg2 = {2, "Thread-B"};
    
    pthread_create(&tid1, NULL, thread_routine, &arg1);
    pthread_create(&tid2, NULL, thread_routine, &arg2);
    
    sleep(1);
    return 0;
}

三、线程终止的三种方式

线程可以通过三种方式终止:

方式1: 从线程函数return
方式2: 调用pthread_exit主动退出
方式3: 被其他线程调用pthread_cancel取消

注意: main函数return会终止整个进程!

3.1 方式1:return返回

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

void* thread_routine(void* arg) {
    printf("子线程开始执行\n");
    
    int* result = (int*)malloc(sizeof(int));
    *result = 100;
    
    printf("子线程即将返回\n");
    return (void*)result;  // 返回值可以被pthread_join获取
}

int main() {
    pthread_t tid;
    void* ret_val;
    
    pthread_create(&tid, NULL, thread_routine, NULL);
    
    // 等待线程(后面详细讲解)
    pthread_join(tid, &ret_val);
    
    printf("子线程返回值: %d\n", *(int*)ret_val);
    free(ret_val);
    
    return 0;
}

📌 注意事项:

1. 不要返回栈上变量的地址!
   void* thread_routine(void* arg) {
       int result = 100;
       return &result;  // 错误! result是栈上变量,函数返回后失效
   }

2. 可以返回:
   ✓ malloc分配的堆内存地址
   ✓ 全局变量的地址
   ✓ 静态变量的地址
   ✓ 整数值(强制转换为指针,不解引用)

3.2 方式2:pthread_exit退出

void pthread_exit(void *value_ptr);

参数:

value_ptr: 线程的退出状态
- 可以被pthread_join获取
- 不要指向栈上的局部变量

示例代码:

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

void* thread_routine(void* arg) {
    printf("子线程执行中...\n");
    
    int* result = (int*)malloc(sizeof(int));
    *result = 200;
    
    printf("子线程调用pthread_exit退出\n");
    pthread_exit((void*)result);  // 主动退出
    
    // 后面的代码不会执行
    printf("这句不会打印\n");
    return NULL;
}

int main() {
    pthread_t tid;
    void* ret_val;
    
    pthread_create(&tid, NULL, thread_routine, NULL);
    pthread_join(tid, &ret_val);
    
    printf("子线程退出码: %d\n", *(int*)ret_val);
    free(ret_val);
    
    return 0;
}

pthread_exit vs return:

相同点:
- 都能终止线程
- 都能设置退出状态

不同点:
1. pthread_exit可以在任何函数中调用
   - 不一定在线程函数中
   - 可以在线程调用的任何子函数中

2. return只能在线程函数中使用
   - 如果在子函数中return,只是退出子函数
   - 不会终止线程

示例:
void helper() {
    pthread_exit(NULL);  // OK,终止线程
    return;              // 只是退出helper函数
}

void* thread_routine(void* arg) {
    helper();
    printf("如果helper用return,这里会执行\n");
    printf("如果helper用pthread_exit,这里不会执行\n");
    return NULL;
}

⚠️ main函数中的pthread_exit:

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_routine, NULL);
    
    pthread_exit(NULL);  // 主线程退出,但进程不终止!
    // 子线程继续运行
}

// 对比:
int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_routine, NULL);
    
    return 0;  // 进程终止,所有线程都终止!
}

3.3 方式3:pthread_cancel取消线程

int pthread_cancel(pthread_t thread);

参数:

thread: 要取消的线程ID

返回值:
- 成功返回0
- 失败返回错误码

示例代码:

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

void* thread_routine(void* arg) {
    int i = 0;
    while (1) {
        printf("子线程运行中... %d\n", i++);
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid;
    void* ret_val;
    
    pthread_create(&tid, NULL, thread_routine, NULL);
    
    sleep(3);  // 让子线程运行3秒
    
    printf("主线程取消子线程\n");
    pthread_cancel(tid);
    
    pthread_join(tid, &ret_val);
    
    if (ret_val == PTHREAD_CANCELED) {
        printf("子线程被取消了\n");
    }
    
    return 0;
}

运行结果:

$ ./thread_cancel
子线程运行中... 0
子线程运行中... 1
子线程运行中... 2
主线程取消子线程
子线程被取消了

📌 取消点(Cancellation Point):

问题: pthread_cancel是立即生效吗?

答: 不是! 线程只有到达取消点才会真正被取消

取消点包括:
- sleep、read、write等阻塞系统调用
- pthread_testcancel(主动设置取消点)

如果线程一直在计算(没有调用阻塞函数),可能无法被取消:
void* thread_routine(void* arg) {
    while (1) {
        // 纯计算,没有取消点
    }
    return NULL;  // 永远执行不到
}

解决: 手动设置取消点
void* thread_routine(void* arg) {
    while (1) {
        // 计算...
        pthread_testcancel();  // 检查是否被取消
    }
    return NULL;
}

四、线程等待pthread_join

4.1 为什么需要线程等待

问题1: 线程资源泄漏
- 线程退出后,其资源(栈、TCB等)不会自动释放
- 需要其他线程pthread_join来回收资源
- 否则会造成资源泄漏

问题2: 获取线程退出状态
- 有时需要知道线程的返回值
- pthread_join可以获取线程的退出状态

问题3: 同步
- 主线程可能需要等待子线程完成某个任务
- pthread_join提供了一种同步机制

4.2 pthread_join函数

int pthread_join(pthread_t thread, void **value_ptr);

参数:

thread: 要等待的线程ID

value_ptr: 输出参数,存储线程的返回值
- 如果不关心返回值,可以传NULL
- 它是二级指针,指向一个void*指针

返回值:
- 成功返回0
- 失败返回错误码

函数行为:

1. 调用pthread_join的线程会阻塞
2. 直到目标线程退出
3. 回收目标线程的资源
4. 获取目标线程的退出状态(如果value_ptr非NULL)

4.3 获取线程返回值

4.3.1 return返回值
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* thread1(void* arg) {
    printf("thread1 returning...\n");
    int* p = (int*)malloc(sizeof(int));
    *p = 100;
    return (void*)p;
}

int main() {
    pthread_t tid;
    void* ret;
    
    pthread_create(&tid, NULL, thread1, NULL);
    pthread_join(tid, &ret);
    
    printf("thread1 return code: %d\n", *(int*)ret);
    free(ret);
    
    return 0;
}
4.3.2 pthread_exit退出值
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* thread2(void* arg) {
    printf("thread2 exiting...\n");
    int* p = (int*)malloc(sizeof(int));
    *p = 200;
    pthread_exit((void*)p);
}

int main() {
    pthread_t tid;
    void* ret;
    
    pthread_create(&tid, NULL, thread2, NULL);
    pthread_join(tid, &ret);
    
    printf("thread2 exit code: %d\n", *(int*)ret);
    free(ret);
    
    return 0;
}
4.3.3 pthread_cancel的返回值
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread3(void* arg) {
    while (1) {
        printf("thread3 running...\n");
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid;
    void* ret;
    
    pthread_create(&tid, NULL, thread3, NULL);
    sleep(3);
    
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    
    if (ret == PTHREAD_CANCELED) {
        printf("thread3 was canceled\n");
    }
    
    return 0;
}

4.4 完整示例:三种终止方式

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

// 线程1: return返回
void* thread1(void* arg) {
    printf("thread1 returning...\n");
    int* p = (int*)malloc(sizeof(int));
    *p = 100;
    return (void*)p;
}

// 线程2: pthread_exit退出
void* thread2(void* arg) {
    printf("thread2 exiting...\n");
    int* p = (int*)malloc(sizeof(int));
    *p = 200;
    pthread_exit((void*)p);
}

// 线程3: 等待被cancel
void* thread3(void* arg) {
    while (1) {
        printf("thread3 running...\n");
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2, tid3;
    void* ret;
    
    // 测试return
    printf("=== 测试return ===\n");
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_join(tid1, &ret);
    printf("thread1 return code: %d\n", *(int*)ret);
    free(ret);
    
    // 测试pthread_exit
    printf("\n=== 测试pthread_exit ===\n");
    pthread_create(&tid2, NULL, thread2, NULL);
    pthread_join(tid2, &ret);
    printf("thread2 exit code: %d\n", *(int*)ret);
    free(ret);
    
    // 测试pthread_cancel
    printf("\n=== 测试pthread_cancel ===\n");
    pthread_create(&tid3, NULL, thread3, NULL);
    sleep(3);
    pthread_cancel(tid3);
    pthread_join(tid3, &ret);
    if (ret == PTHREAD_CANCELED) {
        printf("thread3 was canceled\n");
    }
    
    return 0;
}


编译运行:

$ gcc thread_exit.c -o thread_exit -lpthread
$ ./thread_exit
=== 测试return ===
thread1 returning...
thread1 return code: 100

=== 测试pthread_exit ===
thread2 exiting...
thread2 exit code: 200

=== 测试pthread_cancel ===
thread3 running...
thread3 running...
thread3 running...
thread3 was canceled

4.5 pthread_join的注意事项

注意1: 只能join一次
- 同一个线程只能被join一次
- 多次join同一个线程会导致未定义行为

注意2: 不能join自己
- 线程不能join自己,否则会死锁

注意3: 不能join已分离的线程
- 分离线程(detached)不能被join
- 会返回EINVAL错误

注意4: join是阻塞操作
- 调用线程会一直阻塞,直到目标线程退出
- 如果目标线程一直运行,调用线程就一直等待

五、线程分离pthread_detach

5.1 什么是线程分离

默认情况下,线程是joinable的:
- 线程退出后,资源不会立即释放
- 需要其他线程调用pthread_join回收资源
- 如果不join,会造成资源泄漏

分离线程(detached thread):
- 线程退出后,资源自动释放
- 不需要也不能被join
- 适用于不关心线程退出状态的场景

形象比喻:

joinable线程:
- 就像临时工,工作完成后需要老板(其他线程)来结算工资
- 如果老板不来结算(join),临时工就一直占着工位(资源泄漏)

detached线程:
- 就像自由职业者,工作完成后自己走人,自动清理工位
- 不需要老板结算(不能join)

5.2 pthread_detach函数

int pthread_detach(pthread_t thread);

参数:

thread: 要分离的线程ID
- 可以是其他线程的ID
- 也可以是自己的ID(通过pthread_self()获取)

返回值:
- 成功返回0
- 失败返回错误码

5.3 线程分离示例

5.3.1 线程自己分离
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread_routine(void* arg) {
    // 线程自己分离
    pthread_detach(pthread_self());
    
    printf("子线程运行中...\n");
    sleep(2);
    printf("子线程退出(资源会自动释放)\n");
    
    return NULL;
}

int main() {
    pthread_t tid;
    
    pthread_create(&tid, NULL, thread_routine, NULL);
    
    sleep(1);  // 让子线程先分离
    
    // 尝试join分离的线程(会失败)
    int ret = pthread_join(tid, NULL);
    if (ret != 0) {
        printf("join失败(线程已分离): %s\n", strerror(ret));
    }
    
    return 0;
}

5.3.2 其他线程分离
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread_routine(void* arg) {
    printf("子线程运行中...\n");
    sleep(3);
    printf("子线程退出\n");
    return NULL;
}



int main() {
    pthread_t tid;
    
    pthread_create(&tid, NULL, thread_routine, NULL);
    
    // 主线程分离子线程
    pthread_detach(tid);
    printf("主线程已分离子线程\n");
    
    // 不能join已分离的线程
    // pthread_join(tid, NULL);  // 这会失败
    
    sleep(4);  // 等待子线程完成
    return 0;
}

5.4 joinable vs detached对比

┌──────────────┬─────────────────┬─────────────────┐
│   特性       │   joinable      │   detached      │
├──────────────┼─────────────────┼─────────────────┤
│ 默认状态     │ 是              │ 否              │
├──────────────┼─────────────────┼─────────────────┤
│ 资源回收     │ 需要join        │ 自动释放        │
├──────────────┼─────────────────┼─────────────────┤
│ 可以join     │ 是              │ 否              │
├──────────────┼─────────────────┼─────────────────┤
│ 获取退出状态 │ 可以            │ 不可以          │
├──────────────┼─────────────────┼─────────────────┤
│ 使用场景     │ 需要同步或      │ 不关心退出状态  │
│              │ 获取返回值      │ 的后台任务      │
└──────────────┴─────────────────┴─────────────────┘

5.5 资源泄漏演示

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdint.h>    // 为了intptr_t

void* thread_routine(void* arg) {
    int id = (intptr_t)arg;  // 直接传值转换
    printf("子线程 %d 退出\n", id);
    return NULL;
}

int main() {
    pthread_t tid;
    int i;
    
    // 创建10个线程,但不join也不detach(故意演示资源泄漏)
    // 注意:这里使用 (void*)(intptr_t)i 安全传值,避免局部变量地址问题
    for (i = 0; i < 10; i++) {
        pthread_create(&tid, NULL, thread_routine, (void*)(intptr_t)i);
        sleep(1);  // 让线程有时间退出
    }
    
    printf("按Ctrl+C退出,观察进程状态\n");
    while (1) {
        sleep(1);
    }
    
    return 0;
}

查看资源泄漏:

# 运行程序
$ ./leak &
[1] 12345

# 查看线程数
$ ps -Lf -p 12345
UID   PID  PPID   LWP  C NLWP    SZ   RSS PSR STIME TTY  TIME CMD
user 12345 12340 12345 0   11  1234  5678   0 10:00 pts/0 00:00:00 ./leak
user 12345 12340 12346 0   11  1234  5678   1 10:00 pts/0 00:00:00 ./leak
...

# NLWP=11(1个主线程 + 10个已退出的子线程)
# 虽然子线程已经退出,但因为没有join或detach,资源仍被占用(这就是资源泄漏)
# 解决方法:
# 1. 使用 pthread_join 回收
# 2. 使用 pthread_detach 自动释放


六、本篇总结

📌 核心知识回顾

1. POSIX线程库

  • 头文件:#include <pthread.h>
  • 编译选项:-lpthread
  • 错误处理:返回错误码(不是-1)

2. 线程创建pthread_create

  • int pthread_create(pthread_t*, attr, func, arg)
  • 线程函数签名:void* func(void*)
  • pthread_t是线程控制块的地址

3. 线程ID

  • pthread_t:pthread库维护,进程级唯一
  • LWP:内核维护,系统级唯一
  • 主线程LWP == PID

4. 线程终止三种方式

  • return:从线程函数返回
  • pthread_exit:主动退出,可在任何函数中调用
  • pthread_cancel:取消其他线程

5. 线程等待pthread_join

  • 阻塞等待线程退出
  • 回收线程资源
  • 获取线程退出状态
  • 只能join一次

6. 线程分离pthread_detach

  • 线程退出后自动释放资源
  • 不能被join
  • 适用于不关心退出状态的场景

7. 资源管理

  • joinable线程必须被join,否则资源泄漏
  • detached线程自动释放资源
  • 不能既join又detach

七、实战练习

为了巩固本篇知识,建议完成以下练习:

练习1:多线程计算

// 创建N个线程,每个线程计算一段数组的和
// 最后主线程汇总所有结果

练习2:生产者消费者(简化版)

// 一个线程生产数据,另一个线程消费数据
// 使用全局变量共享数据(先不考虑同步问题)

练习3:线程池雏形

// 创建固定数量的工作线程
// 主线程分配任务给工作线程
// 使用detach管理线程生命周期

八、承上启下

本篇我们掌握了pthread库的核心API,能够创建、终止、等待和分离线程。但还有几个重要问题没有解决:

疑问1: pthread_t到底指向哪里?
- pthread_t是一个地址
- 这个地址在进程地址空间的哪个区域?

疑问2: 线程栈在哪里?
- 主线程的栈在栈区
- 子线程的栈在哪?

疑问3: 线程如何共享进程资源?
- 代码段、数据段、堆是如何共享的?
- 为什么线程之间可以直接访问全局变量?

下一篇预告:

在第三篇中,我们将深入探讨:

✓ pthread_t的本质(线程控制块TCB)
✓ 线程栈的位置(共享区/文件映射区)
✓ 主线程栈vs子线程栈的区别
✓ 进程地址空间的完整布局
✓ 线程如何共享进程资源
✓ 线程栈的大小限制
✓ 线程封装与设计

💬 互动环节

学完本篇,你应该能够:

  • 创建线程并传递参数
  • 使用三种方式终止线程
  • 正确回收线程资源
  • 理解joinable和detached的区别
  • 避免资源泄漏

如果这些你都掌握了,说明你已经具备了多线程编程的基本能力!

👍 如果本文对你有帮助,请点赞、收藏、分享!

💭 有疑问欢迎评论区讨论,我会及时回复!


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐