当前位置: 首页 > news >正文

解码线程编程

线程的概念

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

image

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

image

核心特点:

  • 同一进程内的所有线程共享进程资源(如内存空间、文件描述符),也可拥有私有资源(如线程自身的栈空间)。
  • 操作系统调度线程而非进程,线程切换开销远小于进程切换。
  • 主线程若通过 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选项(链接线程库)。
  • 示例编译命令: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;
}

属性操作函数

image

http://www.dtcms.com/a/618806.html

相关文章:

  • 杰恩设计网站是谁做的百度一下百度一下百度一下
  • 哈希表和冲突处理
  • seo网站建设是什么wordpress首页加广告位
  • 网站开发所需硬件山东建设银行官方网站
  • Adversarial AtA学习(第二十三周周报)
  • 阿里云企业网站备案农村建设自己的网站首页
  • Unity UI框架笔记
  • 15个html5手机网站模板深圳万户网络技术有限公司
  • 网站开发是编程吗网站logoico怎么做
  • 力扣-二叉树的前序遍历
  • 安全联盟可信任网站认证 网站商城系统app开发
  • 做网站的 深圳没有网站做APP
  • 淘宝软件营销网站建设网站建设公司业务培训
  • 【第二十二周】自然语言处理的学习笔记06
  • 重庆ssc做号网站整站外包优化公司
  • Java数据结构-List-栈-队列-二叉树-堆
  • 如何在godaddy空间做手机网站资源wordpress
  • 【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷 5
  • 建立网站需要注册公司吗小制作废品利用
  • thinkphp做直播网站wordpress极验验证注册
  • 世赛网站开发拉新推广
  • SLAM中的非线性优-3D图优化之李群李代数在Opencv-PNP中的应用(四)
  • 做网站什么分类流量多动漫设计与制作学校
  • Vue 项目实战《尚医通》,预约挂号就诊人组件搭建上,笔记40
  • 网站报价方案 模板医疗网站专题怎样做
  • 南京营销型网站商业网站的相关内容
  • 做实验教学视频的网站亚马逊关键词搜索器
  • 差异功能定位解析:C语言与C++(区别在哪里?)
  • 【c++中间件】spdlog日志介绍 二次封装
  • 设计网站中如何设置特效wordpress自定义短码