解码线程编程
线程的概念
进程是正在运行的程序,是操作系统分配资源(代码、数据、内存、文件资源等)的基本单位。线程是进程内的执行单元,是操作系统调度的最小单位,一个进程至少包含 1 个主线程(程序默认的 main 函数执行流),也可创建多个子线程。

线程与进程的关系类似工厂与工人:进程(工厂)提供所有资源,多个线程(工人)共享这些资源,各自独立执行特定任务,协同完成进程的整体功能。

核心特点:
- 同一进程内的所有线程共享进程资源(如内存空间、文件描述符),也可拥有私有资源(如线程自身的栈空间)。
- 操作系统调度线程而非进程,线程切换开销远小于进程切换。
- 主线程若通过 return 或 exit () 终止,整个进程会结束,所有子线程也会随之终止;若主线程调用 pthread_exit (),仅自身终止,子线程可继续运行。
线程的创建
通过pthread_create()函数创建线程,创建成功后线程会立即执行指定的任务函数(线程函数)。
函数原型与核心说明
#include <pthread.h>
/*** @brief 创建一个新线程* @param thread: 输出参数,指向pthread_t类型变量,用于存储创建成功后的线程ID* @param attr: 线程属性指针,NULL表示使用默认属性(可连接状态)* @param start_routine: 线程函数指针,线程创建后会自动执行该函数,函数原型必须为void* (*)(void*)* @param arg: 传递给线程函数的参数,需强转为void*类型,线程函数内再转回原类型* @return int: 成功返回0,失败返回非0错误码(不同错误对应不同错误码,可通过strerror(errno)查看描述)* @note 编译时必须加-lpthread选项链接线程库(如gcc test.c -o test -lpthread)* @warning 传递给arg的参数若为局部变量,需确保线程访问时该变量未被销毁(建议用全局变量或动态内存)*/
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
示例代码(获取触摸屏坐标)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/input.h> // 输入子系统事件结构体定义
#include <unistd.h>
#include <pthread.h> // 线程操作库
#include <errno.h>
#include <string.h> // 用于strerror等错误处理// -------------------------- 硬件分辨率配置(务必根据实际设备修改!) --------------------------
#define TS_WIDTH 1024 // 触摸屏原始X轴范围(例:0~1023,通过硬件手册或工具查询)
#define TS_HEIGHT 600 // 触摸屏原始Y轴范围(例:0~599)
#define LCD_WIDTH 800 // 目标LCD屏幕X轴像素范围(例:0~799)
#define LCD_HEIGHT 480 // 目标LCD屏幕Y轴像素范围(例:0~479)// -------------------------- 全局变量(线程间共享数据) --------------------------
int g_ts_x = 0; // 触摸屏原始X坐标(子线程写入,主线程读取)
int g_ts_y = 0; // 触摸屏原始Y坐标
int g_lcd_x = 0; // 转换后的LCD X坐标
int g_lcd_y = 0; // 转换后的LCD Y坐标
int g_ts_fd = -1; // 触摸屏设备文件描述符(主线程打开,子线程使用)// -------------------------- 函数实现 --------------------------
void ts_to_lcd(int ts_x, int ts_y, int *lcd_x, int *lcd_y) {// 空指针校验:避免非法访问导致程序崩溃if (lcd_x == NULL || lcd_y == NULL) {fprintf(stderr, "[ERROR] ts_to_lcd: 输出参数lcd_x/lcd_y不可为NULL\n");return;}// 核心转换逻辑:线性映射(保持坐标比例一致)*lcd_x = ts_x * LCD_WIDTH / TS_WIDTH;*lcd_y = ts_y * LCD_HEIGHT / TS_HEIGHT;// 边界保护:确保坐标在LCD有效范围内(防止超出屏幕显示区域)*lcd_x = (*lcd_x < 0) ? 0 : ((*lcd_x >= LCD_WIDTH) ? (LCD_WIDTH - 1) : *lcd_x);*lcd_y = (*lcd_y < 0) ? 0 : ((*lcd_y >= LCD_HEIGHT) ? (LCD_HEIGHT - 1) : *lcd_y);
}void *ts_read_thread(void *arg) {// 设备合法性校验:确保主线程已成功打开触摸屏if (g_ts_fd < 0) {fprintf(stderr, "[ERROR] ts_read_thread: 触摸屏设备未初始化(fd=%d)\n", g_ts_fd);pthread_exit(NULL); // 线程异常退出}struct input_event ts_event; // 存储单个输入事件(触摸、同步等)int coord_cnt = 0; // 坐标计数:累计获取X+Y(满2个表示坐标完整)printf("[THREAD] 触摸读取线程启动,等待触摸事件...\n");// 死循环:持续读取触摸事件(无事件时阻塞,不占用CPU资源)while (1) {int ret = read(g_ts_fd, &ts_event, sizeof(ts_event));if (ret != sizeof(ts_event)) { // 读取不完整(设备异常或数据损坏)fprintf(stderr, "[ERROR] ts_read_thread: 读取触摸事件失败,原因:%s\n", strerror(errno));close(g_ts_fd); // 关闭设备,避免资源泄漏g_ts_fd = -1; // 标记设备已失效pthread_exit(NULL);}// -------------------------- 解析输入事件类型 --------------------------// 绝对坐标事件(EV_ABS):提取X/Y轴坐标if (ts_event.type == EV_ABS) {if (ts_event.code == ABS_X) { // X轴坐标事件g_ts_x = ts_event.value; // 更新全局原始X坐标coord_cnt++; // 计数+1} else if (ts_event.code == ABS_Y) { // Y轴坐标事件g_ts_y = ts_event.value; // 更新全局原始Y坐标coord_cnt++; // 计数+1}}// 触摸松开事件(EV_KEY+BTN_TOUCH+value=0):手指离开屏幕时重置计数else if (ts_event.type == EV_KEY && ts_event.code == BTN_TOUCH && ts_event.value == 0) {coord_cnt = 0; // 避免下次触摸残留旧计数}// -------------------------- 坐标完整时执行转换 --------------------------if (coord_cnt >= 2) { // 已获取完整X+Y坐标ts_to_lcd(g_ts_x, g_ts_y, &g_lcd_x, &g_lcd_y); // 转换为LCD坐标coord_cnt = 0; // 重置计数,等待下一次触摸}}// 理论上不会执行到此处(循环为死循环)pthread_exit(NULL);
}// -------------------------- 主函数(程序入口) --------------------------
int main(int argc, char const *argv[]) {pthread_t ts_thread_id; // 触摸读取子线程IDg_ts_fd = open("/dev/input/event0", O_RDWR);if (g_ts_fd == -1) {fprintf(stderr, "[ERROR] main: 打开触摸屏设备失败,原因:%s\n", strerror(errno));fprintf(stderr, "[TIP] 1. 确认设备路径是否正确 2. 用sudo运行程序(输入设备需root权限)\n");exit(1); // 程序异常退出}printf("[MAIN] 触摸屏设备打开成功,设备描述符fd=%d\n", g_ts_fd);int ret = pthread_create(&ts_thread_id, NULL, ts_read_thread, NULL);if (ret != 0) {fprintf(stderr, "[ERROR] main: 创建触摸线程失败,错误码=%d,原因:%s\n", ret, strerror(ret));close(g_ts_fd); // 清理资源exit(1);}printf("[MAIN] 触摸读取子线程创建成功,线程ID=%lu\n", (unsigned long)ts_thread_id);// 主线程不参与触摸读取,专注于LCD坐标的应用(如显示到屏幕、数据传输等)while (1) {printf("[MAIN] 原始坐标=(%4d, %4d) → LCD坐标=(%4d, %4d)\n",g_ts_x, g_ts_y, g_lcd_x, g_lcd_y);sleep(1); // 延时1秒,避免打印刷屏(实际项目可替换为LCD显示代码)}pthread_join(ts_thread_id, NULL); // 等待子线程退出(回收线程资源)close(g_ts_fd); // 关闭设备文件(释放fd)printf("[MAIN] 程序正常退出\n");return 0;
}
编译链接注意事项
- 如果遇到
- arm_test.c:(.text+0x3f4): undefined reference to `pthread_create’
collect2: error: ld returned 1 exit status - 编译须加
lpthread选项(链接线程库)。
- arm_test.c:(.text+0x3f4): undefined reference to `pthread_create’
- 示例编译命令:
gcc thread_demo.c -o thread_demo -lpthread。
线程的结束
线程结束有多种方式,核心是安全终止线程并避免资源泄漏,常用方式为pthread_exit()函数。
线程结束的三种合法方式
- 调用
pthread_exit(void *retval):主动终止当前线程, retval 为退出状态(供其他线程获取)。 - 线程函数执行 return:效果等同于在 return 处调用
pthread_exit(返回值)。 - 其他线程调用
pthread_cancel(pthread_t thread):取消指定线程(需线程支持取消机制)。
关键区别
pthread_exit():仅终止当前线程,进程及其他线程不受影响。exit(int status):终止整个进程,所有线程都会被强制终止。- 主线程 return:等同于调用
exit(return值),终止整个进程。
pthread_exit () 函数详解
#include <pthread.h>
/*** @brief 终止当前线程* @param retval: 线程退出状态指针,若其他线程调用pthread_join可获取该值* @return 无返回值(函数调用后不会回到调用点)* @note retval指向的内存不能是线程栈上的局部变量(线程终止后栈空间会释放)* @warning 若线程是可连接状态,retval需指向全局变量或动态分配的内存(避免野指针)*/
void pthread_exit(void *retval);
示例代码(线程主动退出)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 子线程1:执行一次任务后退出
void *task_1(void *arg) {printf("task_1 start\n");sleep(2); // 模拟任务执行printf("task_1 exit\n");// 退出状态返回10(需用(void*)强转,因返回类型为void*)pthread_exit((void *)10);
}// 子线程2:无限循环运行
void *task_2(void *arg) {while (1) {printf("task_2 running\n");sleep(1);}
}int main(int argc, char const *argv[]) {pthread_t thread_1, thread_2;// 创建线程1pthread_create(&thread_1, NULL, task_1, NULL);// 创建线程2pthread_create(&thread_2, NULL, task_2, NULL);// 主线程睡眠3秒后退出(仅终止主线程,线程2继续运行)sleep(3);printf("main thread exit\n");pthread_exit(NULL);// 以下代码不会执行return 0;
}
线程的接合(回收资源)
线程退出后不会立即释放资源,会变为 “僵尸线程”,需通过pthread_join()函数回收资源并获取退出状态。
pthread_join () 函数详解
#include <pthread.h>
/*** @brief 等待指定线程终止,回收其资源并获取退出状态* @param thread: 要等待的线程ID(创建线程时通过pthread_create获取)* @param retval: 输出参数,用于存储线程的退出状态(对应pthread_exit的retval)* 若线程被取消,retval会被设为PTHREAD_CANCELED* @return int: 成功返回0,失败返回非0错误码* @note 该函数是阻塞函数,若目标线程未终止,调用线程会一直等待* @warning 同一线程不能被多个线程同时join,否则结果未定义* 若不需要获取退出状态,retval可设为NULL*/
int pthread_join(pthread_t thread, void **retval);
pthread_tryjoin_np()非阻塞接合
#include <pthread.h>/*** @brief 非阻塞尝试接合目标线程,目标未终止则直接返回错误* @param thread: 目标线程的用户态ID(同pthread_join)* @param retval: 输出参数,存储退出状态(同pthread_join)* @return int: 成功返回0;失败返回错误码(EBUSY:目标线程未终止,EINVAL:不可接合等)* @note 1. 后缀np(non-portable):非POSIX标准,Linux特有(需包含<pthread.h>,编译无需额外选项)* 2. 适用场景:避免调用线程被长时间阻塞(如主线程需定期处理其他任务,同时检查子线程是否结束)*/
int pthread_tryjoin_np(pthread_t thread, void **retval);
pthread_timedjoin_np()超时接合
#include <pthread.h>
#include <time.h> // 需包含时间相关头文件/*** @brief 超时等待接合目标线程,超时后返回错误* @param thread: 目标线程的用户态ID(同pthread_join)* @param retval: 输出参数,存储退出状态(同pthread_join)* @param abstime: 绝对超时时间(struct timespec类型),需设置为“当前时间 + 超时时长”* @return int: 成功返回0;失败返回错误码(ETIMEDOUT:超时,EBUSY:未超时但线程未终止等)* @note 绝对超时时间:需用clock_gettime()获取当前时间,再加上超时时长(避免系统时间变化导致误差)* 适用场景:限制接合的最大等待时间(如主线程最多等待5秒,超时则放弃接合并处理其他逻辑)*/
int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime);
pthread_self()函数详解
#include <pthread.h>/*** @brief 获取调用该函数的当前线程的用户态ID(TID)* @param 无参数* @return pthread_t: 当前线程的ID,类型为pthread_t(具体实现可能是结构体指针或整数,不可直接用%d打印)* @note 线程ID的作用域:仅在当前进程内有效,不同进程的pthread_t可能重复,不能跨进程使用* 与内核态TID的区别:内核态TID通过系统调用gettid()获取(返回int类型,内核全局唯一),二者不可混用* 打印方式:Linux下pthread_t通常是unsigned long类型,建议用%lu格式化输出(避免移植性问题)* 核心用途:线程自我标识(如自我分离、日志打印线程ID、判断当前线程是否为主线程等)*/
pthread_t pthread_self(void);
示例代码(回收线程资源并获取退出状态)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 存储主线程ID(供子线程使用)
#include <errno.h>
#include <string.h>
pthread_t main_thread;/*** @brief 子线程任务:等待主线程退出并打印其退出状态* @param arg: 无参数(设为NULL)* @return void*: 线程退出状态*/
void *task(void *arg) {void *exit_status;printf("sub thread waiting for main thread...\n");// 阻塞等待主线程终止,获取退出状态int ret = pthread_join(main_thread, &exit_status);if (ret != 0) {fprintf(stderr, "pthread_join error[%d],%s\n", errno,strerror(errno));pthread_exit(NULL);}// 打印主线程退出状态(需转回int类型)printf("main thread exit status: %d\n", *((int*)exit_status));// 子线程退出pthread_exit(NULL);
}int main(int argc, char const *argv[]) {pthread_t sub_thread;// 创建子线程pthread_create(&sub_thread, NULL, task, NULL);// 获取主线程自身IDmain_thread = pthread_self();printf("main thread ID: %lu\n", (unsigned long)main_thread);// 主线程睡眠5秒后退出,返回状态100sleep(5);printf("main thread is exiting...\n");// 退出状态用全局变量或动态内存(此处用全局变量地址,避免栈空间释放问题)static int status = 100;pthread_exit((void *)&status);return 0;
}
关键注意事项
- 若线程未被 join 且未设置分离属性,会一直占用资源(僵尸线程)。
- 若不需要获取线程退出状态,可将线程设为分离属性(无需 join)。
线程的属性
线程属性通过pthread_attr_t结构体管理,核心属性包括分离状态、栈大小、调度策略等,重点是分离属性(解决僵尸线程问题)。
线程属性的基本操作流程
- 定义属性对象:
pthread_attr_t attr - 初始化属性对象:
pthread_attr_init(pthread_attr_t *attr)。 - 设置属性(如分离状态):
pthread_attr_setdetachstate()。 - 用该属性创建线程:
pthread_create()的第二个参数传入属性对象。 - 销毁属性对象:
pthread_attr_destroy(pthread_attr_t *attr)(属性对象不再使用时)。
核心属性:分离状态
分离状态决定线程终止后资源的释放方式:
- 可连接状态(默认):线程终止后需调用
pthread_join()回收资源。 - 分离状态:线程终止后自动释放资源,无需 join(不能被 join)。
核心属性操作函数
初始化与销毁属性对象
#include <pthread.h>
/*** @brief 初始化线程属性对象(设置为默认属性)* @param attr: 指向pthread_attr_t类型的指针* @return int: 成功返回0,失败返回非0错误码* @note 必须先初始化,才能设置其他属性*/
int pthread_attr_init(pthread_attr_t *attr);/*** @brief 销毁线程属性对象(释放资源)* @param attr: 已初始化的属性对象指针* @return int: 成功返回0,失败返回非0错误码* @note 销毁后属性对象不可再使用,除非重新初始化*/
int pthread_attr_destroy(pthread_attr_t *attr);
设置分离状态(关键属性)
#include <pthread.h>
/*** @brief 设置线程的分离状态(决定线程是否可接合)* @param attr: 已初始化的属性对象指针* @param detachstate: 分离状态值,可选:* - PTHREAD_CREATE_DETACHED:分离状态(自动回收资源,不可接合)* - PTHREAD_CREATE_JOINABLE:可连接状态(默认,需pthread_join回收)* @return int: 成功返回0,失败返回非0错误码* @note 分离状态的线程终止后,系统自动释放资源,无需其他线程接合*/
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);/*** @brief 获取线程属性对象的分离状态* @param attr: 属性对象指针* @param detachstate: 输出参数,存储当前分离状态* @return int: 成功返回0,失败返回非0错误码*/
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
动态设置分离状态(线程创建后)
#include <pthread.h>
/*** @brief 动态将目标线程设置为分离状态(线程创建后使用)* @param thread: 目标线程的ID* @return int: 成功返回0,失败返回非0错误码* @note 1. 只能对“可连接状态”的线程调用,已分离线程调用结果未定义* 2. 线程自身可通过pthread_self()获取ID,实现自我分离*/
int pthread_detach(pthread_t thread);
示例:创建时设置分离属性(pthread_attr_setdetachstate)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
/*** @brief 子线程任务:输出线程ID后退出* @param arg: 传递的字符串参数* @return void*: 退出状态*/
void *task(void *arg) {// 打印传递的参数和自身IDprintf("%s, my TID: %lu\n", (char *)arg, (unsigned long)pthread_self());// 线程退出,资源自动释放(因是分离属性)pthread_exit(NULL);
}int main(int argc, char const *argv[]) {// 定义线程属性对象pthread_attr_t thread_attr;// 始化属性对象(设置为默认属性)int ret = pthread_attr_init(&thread_attr);if (ret != 0) {fprintf(stderr, "pthread_attr_init error[%d]%s\n", errno, strerror(errno));exit(1);}// 置属性为分离状态(PTHREAD_CREATE_DETACHED)ret = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);if (ret != 0) {fprintf(stderr, "set detachstate error[%d]%s\n", errno, strerror(errno));pthread_attr_destroy(&thread_attr);exit(1);}// 用该属性创建两个子线程pthread_t thread_a, thread_b;// 子线程A:传递"hello"pthread_create(&thread_a, &thread_attr, task, (void *)"hello");// 子线程B:传递"world"pthread_create(&thread_b, &thread_attr, task, (void *)"world");// 主线程睡眠1秒,确保子线程完成任务(否则主线程提前退出会终止子线程)sleep(1);printf("main thread exit\n");// 销毁属性对象(创建线程后即可销毁,不影响已创建的线程)pthread_attr_destroy(&thread_attr);return 0;
}
示例:线程创建后设置分离属性(pthread_detach)
若创建线程时未设置分离属性,可在线程函数内调用pthread_detach()强制设置:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
void *task(void *arg) {// 强制将当前线程设为分离属性(参数为自身ID,通过pthread_self()获取)int ret = pthread_detach(pthread_self());if (ret != 0) {fprintf(stderr, "pthread_detach error[%d]%s\n",errno, strerror(errno));pthread_exit(NULL);}printf("I am thread, my TID: %lu\n", (unsigned long)pthread_self());pthread_exit(NULL); // 终止后自动释放资源
}int main(int argc, char const *argv[]) {pthread_t thread;// 创建线程(默认可连接状态)pthread_create(&thread, NULL, task, NULL);// 主线程睡眠1秒,等待子线程执行sleep(1);printf("main thread exit\n");return 0;
}
属性操作函数

