线程
线程概念
线程实际上是应用层的概念,在Linux内核中,所有的调度实体都被称为任务(task),它们之间的区别是:有些任务自己拥有一套完整的资源,而有些任务彼此之间共享一套资源。
- 左边是一个含有单个线程的进程,它拥有自己的一套完整的资源。
- 右边是一个含有两条线程的进程,线程彼此间共享进程内的资源。
由此可见,线程是一种轻量级进程,提供一种高效的任务处理方式。
线程与进程的核心区别
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源占有 | 拥有独立的资源空间(代码段、数据段、堆等) | 共享所属进程的资源空间 |
| 调度单位 | 内核级调度实体(task) | 应用层轻量级调度实体,依赖进程 |
| 切换开销 | 大(需切换资源空间) | 小(仅切换执行上下文) |
| 独立性 | 高(一个进程崩溃不影响其他进程) | 低(一个线程崩溃可能导致整个进程崩溃) |
| 通信方式 | 复杂(管道、消息队列等IPC机制) | 简单(直接访问共享资源) |
进程与线程的应用场景
进程的应用场景
适用于需要独立资源和隔离环境的场景,核心优势是稳定性和安全性:
- 网络服务器、数据库系统:每个进程拥有独立地址空间和资源(文件描述符、内存等),进程间资源不共享,单个进程故障不会影响整体系统。
- 需严格隔离的任务:通过进程间通信(IPC)机制实现数据交换,保证数据安全性。
线程的应用场景
适用于需要高效并发执行的场景,核心优势是资源共享和低开销:
- 图形界面应用(GUI):主线程处理界面交互,子线程处理后台任务(如数据加载、计算),避免界面卡顿。
- 多媒体处理、数据批量处理:线程共享进程内存,数据交换无需复杂IPC,提高执行效率。
- 网络通信并发处理:单个服务进程通过多线程同时响应多个客户端请求,节省内存资源。
线程的内存资源
系统不会为线程分配新的内存空间,线程的所有资源均来自其所属的进程(共享进程的虚拟内存空间),包括代码段、数据段、堆、文件描述符等。线程仅拥有独立的栈空间(用于存储局部变量、函数调用上下文)。
- 线程共享进程的代码段、数据段、堆、全局变量、文件描述符等资源。
- 线程独占独立的栈空间,用于执行自身任务的局部数据存储。
- 注意:线程结束后,需手动或自动回收其栈空间(避免内存泄漏)。
基本接口
线程的创建
创建一条POSIX线程需指定线程的执行函数,函数接口及细节如下:
#include <pthread.h>
// 返回值:成功返回0,失败返回非0错误码
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
参数说明
thread:输出参数,存储新创建线程的TID(线程ID)attr:线程属性,创建标准线程时设为NULL(使用默认属性)start_routine:线程执行函数的地址(函数指针),格式固定为void* (*)(void*)arg:传递给线程执行函数的参数,若无需参数设为NULL
线程参数传递
线程通过pthread_create的arg参数传递数据,本质是传递内存地址,线程函数通过指针解析该地址的数据(共享进程内存,无需拷贝)。
示例代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 线程任务函数:接收并修改参数
void *task(void *arg) {
printf("线程接收的参数地址:%p\n", arg);
// 指针类型转换,访问地址数据
int *p = (int *)arg;
printf("线程接收的参数值:%d\n", *p);
// 修改共享内存中的数据(主线程可见)
*p = 10086;
}
int main() {
int value = 10010; // 主线程中的变量(共享资源)
printf("主线程中value的地址:%p\n", &value);
// 创建线程,传递value的地址
pthread_t tid;
pthread_create(&tid, NULL, task, (void *)&value);
sleep(1); // 确保线程先执行(避免主线程提前退出)
printf("主线程中value修改后的值:%d\n", value); // 输出10086
getchar();
return 0;
}
线程的并发性
线程最重要的特性是并发执行:线程函数与主线程会同时运行,这是它与普通函数调用的根本区别。
核心注意点:竞态问题
由于线程共享进程资源,多个线程同时访问共享资源(如全局变量、堆内存等)时,会出现“竞态”(Race Condition),导致结果不可预期。
线程最重要的特性是并发,线程函数 doSomething() 会与主线程 main() 同时运行,这是它与普通函数调用的根本区别。需要特别提醒的是,由于线程函数的并发性,在线程中访问共享资源需要特别小心,因为这些共享资源会被多个线程争抢,形成“竞态”。最典型的共享资源是全局变量,比如以下代码:
示例代码(含竞态演示)
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
// 标志位:false=资源可读(子线程操作),true=资源可写(主线程操作)
bool flag = true;
// 线程执行函数:判断共享变量的奇偶性
void *myTask(void *arg) {
int *ptr = (int *)arg;
while (1) {
if (flag == false) {
if (*ptr % 2) {
printf("[%d]是一个奇数\n", *ptr);
} else {
printf("[%d]是一个偶数\n", *ptr);
}
flag = true; // 释放写权限
}
}
}
int main(int argc, char const *argv[]) {
int Num = 0; // 共享资源(主线程修改,子线程读取)
pthread_t tid;
// 创建线程,传递共享变量地址
pthread_create(&tid, NULL, myTask, (void *)&Num);
srand(time(NULL)); // 设置随机种子
while (1) {
if (flag == true) {
Num = rand() % 1000; // 修改共享资源
flag = false; // 释放读权限
}
}
return 0;
}
2.3 线程的退出
线程执行完毕后,通过以下接口退出,释放自身资源(不影响进程内其他线程):
#include <pthread.h>
void pthread_exit(void *retval);
参数说明
retval:线程的返回值,可被pthread_join获取;无需返回值时设为NULL
与exit的区别
| 函数 | 作用范围 |
|---|---|
pthread_exit |
仅退出当前线程 |
exit |
退出整个进程(所有线程) |
线程的取消
用于主动终止正在运行的线程,需配合取消状态、取消类型及清理函数使用,避免资源泄漏。
核心接口:发送取消请求
#include <pthread.h>
// 返回值:成功返回0,失败返回非0错误码
int pthread_cancel(pthread_t thread);
thread:需要取消的线程TID
获取当前线程TID
#include <pthread.h>
// 返回值:当前线程的TID
pthread_t pthread_self(void);
控制取消状态与类型
开启/关闭取消请求
#include <pthread.h>
// 返回值:成功返回0,失败返回非0错误码
int pthread_setcancelstate(int state, int *oldstate);
state:取消状态PTHREAD_CANCEL_ENABLE:开启取消(默认)PTHREAD_CANCEL_DISABLE:关闭取消(忽略取消请求)
oldstate:输出参数,存储修改前的旧状态
设置取消类型
#include <pthread.h>
// 返回值:成功返回0,失败返回非0错误码
int pthread_setcanceltype(int type, int *oldtype);
type:取消类型PTHREAD_CANCEL_DEFERRED:延时取消(默认,线程执行到“取消点”才响应)PTHREAD_CANCEL_ASYNCHRONOUS:立即取消(收到请求后立即终止)
取消清理函数
线程被取消时,自动执行的函数,用于释放资源(如打开的文件、堆内存、锁等)。
#include <pthread.h>
// 注册清理函数(必须与pthread_cleanup_pop成对使用,且在同一代码块中)
void pthread_cleanup_push(void (*routine)(void *), void *arg);
// 注销清理函数
void pthread_cleanup_pop(int execute);
routine:清理函数指针(格式:void (*)(void *))arg:传递给清理函数的参数execute:0:线程正常结束时,不执行清理函数1:线程正常结束时,执行清理函数- 注意:无论
execute值如何,线程被取消时都会执行清理函数
示例代码(线程取消与清理)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
FILE *fp = NULL; // 全局文件指针(需在取消时关闭)
// 取消清理函数:释放文件资源
void clear_task(void *arg) {
printf("线程被取消,执行清理函数(关闭文件)\n");
if (fp != NULL) {
fclose(fp);
fp = NULL;
}
}
// 线程任务函数:循环写入文件
void *task(void *arg) {
// 打开文件
fp = fopen("my.txt", "w+");
if (fp == NULL) {
perror("文件打开失败");
pthread_exit(NULL);
}
// 注册清理函数(必须与pop成对出现)
pthread_cleanup_push(clear_task, NULL);
// 设置取消状态为开启(默认开启,此处仅演示)
int old_state;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state);
// 设置取消类型为延时取消(默认)
int old_type;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old_type);
int i = 0;
while (1) {
fputc('Q', fp); // 写入文件(模拟任务)
printf("线程正在写入数据(%d次)\n", ++i);
sleep(1);
if (i == 10) {
break; // 正常结束
}
}
// 正常结束时不执行清理函数(execute=0),手动关闭文件
pthread_cleanup_pop(0);
fclose(fp);
fp = NULL;
printf("线程正常结束\n");
return NULL;
}
int main() {
// 创建线程
pthread_t tid;
pthread_create(&tid, NULL, task, NULL);
// 等待用户输入,触发取消
printf("按任意键取消线程...\n");
getchar();
pthread_cancel(tid); // 发送取消请求
// 回收线程资源
pthread_join(tid, NULL);
return 0;
}
线程的接合
线程退出后不会立即释放系统资源,会成为僵尸线程。通过pthread_join可回收僵尸线程资源,并获取其退出返回值,该操作称为“线程接合”。
核心接口
#include <pthread.h>
// 返回值:成功返回0,失败返回非0错误码
int pthread_join(pthread_t tid, void **val);
接口说明
- 若指定
tid的线程未退出,当前线程会阻塞等待 - 无需获取退出值时,
val设为NULL - 若线程处于分离状态或不存在,函数会出错返回
扩展接合接口(GNU扩展)
#define _GNU_SOURCE /* 必须定义该宏才能使用 */
#include <pthread.h>
// 非阻塞接合:线程未退出时直接返回错误,不阻塞
int pthread_tryjoin_np(pthread_t thread, void **retval);
// 定时阻塞接合:超过指定时间未退出则返回错误
int pthread_timedjoin_np(pthread_t thread, void **retval,
const struct timespec *abstime);
示例代码(三种接合方式)
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
// 选择接合方式:BLOCKING(阻塞)、NON_BLOCKING(非阻塞)、TIME_OUT(定时)
#define BLOCKING 1
#define NON_BLOCKING 0
#define TIME_OUT 0
void *MyTask(void *arg) {
sleep(12); // 模拟线程执行耗时操作
static int Num = 123; // 静态变量(退出后仍存在,可作为返回值)
printf("myTask: Num的地址=%p\n", &Num);
pthread_exit(&Num); // 退出线程并返回Num地址
}
int main(int argc, char const *argv[]) {
pthread_t tid;
pthread_create(&tid, NULL, MyTask, NULL);
int *ret; // 存储线程返回值
#if BLOCKING
// 1. 阻塞等待线程退出
pthread_join(tid, (void **)&ret);
printf("阻塞接合成功,返回值=%d\n", *ret);
#elif NON_BLOCKING
// 2. 非阻塞接合
errno = pthread_tryjoin_np(tid, (void **)&ret);
perror("非阻塞接合结果"); // 线程未退出时会提示错误
#elif TIME_OUT
// 3. 定时阻塞接合(等待2秒)
struct timespec abstime;
abstime.tv_sec = time(NULL) + 2; // 绝对时间(当前时间+2秒)
abstime.tv_nsec = 0;
errno = pthread_timedjoin_np(tid, (void **)&ret, &abstime);
perror("定时接合结果"); // 2秒内未退出会提示超时错误
#endif
printf("Main: 线程返回值地址=%p\n", ret);
return 0;
}
编译注意事项
编译含线程函数的代码时,需手动链接线程库(系统不会默认链接),否则会报“未定义引用”错误:
# 错误编译(无链接线程库)
gcc 线程代码.c
# 报错:undefined reference to `pthread_create'
# 正确编译(添加 -lpthread 链接线程库)
gcc 线程代码.c -lpthread
其他接口:检测文件权限
用于检测指定路径文件的权限(存在性、读写执行权限):
#include <unistd.h>
// 返回值:权限全部满足返回0,否则返回-1
int access(const char *pathname, int mode);
参数说明
pathname:目标文件的路径(绝对路径或相对路径)mode:检测的权限类型(可组合使用,如R_OK | W_OK):R_OK:是否可读W_OK:是否可写X_OK:是否可执行F_OK:文件是否存在
示例用法
#include <stdio.h>
#include <unistd.h>
int main() {
const char *file = "test.txt";
// 检测文件是否存在且可读可写
if (access(file, F_OK | R_OK | W_OK) == 0) {
printf("文件存在,且拥有读写权限\n");
} else {
perror("权限检测失败");
}
return 0;
}
线程的属性
线程拥有多种可配置属性,可通过属性变量统一设置(创建线程时传入pthread_create的attr参数)。
线程属性列表
通过man pthread_attr_(连续按两下Tab)可查看所有属性相关函数,核心属性如下:
| 获取属性API | 功能描述 | 设置属性API | 功能描述 |
|---|---|---|---|
pthread_attr_getdetachstate |
获取线程分离状态 | pthread_attr_setdetachstate |
设置线程分离状态 |
pthread_attr_getguardsize |
获取栈警戒区大小(防止栈溢出) | pthread_attr_setguardsize |
设置栈警戒区大小 |
pthread_attr_getinheritsched |
获取调度策略继承方式 | pthread_attr_setinheritsched |
设置调度策略继承方式 |
pthread_attr_getschedpolicy |
获取线程调度策略(如RR、FIFO) | pthread_attr_setschedpolicy |
设置线程调度策略 |
pthread_attr_getschedparam |
获取调度参数(如优先级) | pthread_attr_setschedparam |
设置调度参数 |
pthread_attr_getscope |
获取线程竞争范围(进程内/系统级) | pthread_attr_setscope |
设置线程竞争范围 |
pthread_attr_getaffinity_np |
获取CPU亲和度(绑定到指定CPU) | pthread_attr_setaffinity_np |
设置CPU亲和度 |
pthread_attr_getstack |
获取栈基地址和栈大小 | pthread_attr_setstack |
设置栈基地址和栈大小 |
pthread_attr_getstacksize |
获取栈大小 | pthread_attr_setstacksize |
设置栈大小 |
属性变量的使用步骤
线程属性需通过pthread_attr_t类型的变量统一管理,使用流程如下:
- 定义并初始化属性变量
- 为属性变量设置具体属性
- 传入
pthread_create创建线程 - 销毁属性变量(释放资源)
核心接口(初始化与销毁)
#include <pthread.h>
// 初始化属性变量(设为默认属性)
int pthread_attr_init(pthread_attr_t *attr);
// 销毁属性变量(释放资源,不可再使用)
int pthread_attr_destroy(pthread_attr_t *attr);
示例代码(设置分离属性)
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
void* TaskFunc(void *arg) {
sleep(2); // 模拟线程执行
pthread_exit(NULL);
}
int main(int argc, char const *argv[]) {
pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化属性变量
// 1. 设置线程为分离状态(退出后自动释放资源,无需join)
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 2. 设置调度策略为时间片轮转(RR)
pthread_attr_setschedpolicy(&attr, SCHED_RR);
// 创建线程(传入属性变量)
pthread_t tid;
pthread_create(&tid, &attr, TaskFunc, NULL);
// 尝试接合分离状态的线程(会失败)
errno = pthread_join(tid, NULL);
if (errno == 0) {
printf("线程接合成功\n");
} else {
perror("join error"); // 输出错误:Invalid argument
}
// 销毁属性变量
pthread_attr_destroy(&attr);
while (1) pause(); // 让主线程不退出
return 0;
}
拓展:动态分离线程
若线程创建时未设置分离属性,可通过以下函数在运行时强制分离:
#include <pthread.h>
// 返回值:成功返回0,失败返回非0错误码
int pthread_detach(pthread_t tid);
线程栈空间的设置
线程栈空间用于存储局部变量和函数调用上下文,默认大小由系统配置(可通过ulimit -a查看,通常为10240KB),可手动设置(需大于PTHREAD_STACK_MIN=16384字节)。
查看系统资源配置
ulimit -a # 查看系统资源限制,重点关注 stack size
示例代码(设置栈大小)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *task(void *arg) {
int i = 0;
while (1) {
printf("线程正在运行(%d次)\n", i++);
sleep(1);
if (i == 10) {
pthread_exit(NULL); // 退出线程
}
}
}
int main() {
// 初始化属性变量
pthread_attr_t attr;
pthread_attr_init(&attr);
// 设置栈大小(2倍最小栈大小)
size_t stack_size = PTHREAD_STACK_MIN * 2;
pthread_attr_setstacksize(&attr, stack_size);
// 创建线程(使用自定义栈大小)
pthread_t tid;
pthread_create(&tid, &attr, task, NULL);
// 获取并打印当前栈大小
size_t get_size = 0;
pthread_attr_getstacksize(&attr, &get_size);
printf("线程栈大小设置为:%ld 字节\n", get_size);
// 回收线程资源
pthread_join(tid, NULL);
// 销毁属性变量
pthread_attr_destroy(&attr);
return 0;
}

浙公网安备 33010602011771号