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

嵌入式学习之系统编程(六)线程

目录

一、线程

(一)线程概念

(二)特征

(三)优缺点

二、线程与进程的区别(面问)

三、多线程程序设计步骤

四、线程的创建(相关函数)

1、pthread_create()

2、pthread_self()

3、扩展:strerror()

五、线程的退出

1、pthread_exit()

2、pthread_cancel()

六、线程的回收

(一)线程的回收机制

(二)线程回收相关函数 pthread_join()

(三)次线程的回收策略

七、线程的参数,返回值

八、分离属性

1、pthread_attr_init()

2、pthread_attr_destroy()

3、pthread_attr_setdetachstate()

4、pthread_deatch()

线程清理函数

5、pthread_cleanup_push()

6、pthread_cleanup_pop()

7、注:

8、示例

一、线程

(一)线程概念

1、线程是轻量级进程,一般是一个进程中的多个任务(一个进程可以有多个线程);

2、进程是系统中最小的资源分配单位;

     线程是系统中最小的执行单位。

(二)特征

1、共享资源:

2、效率高 30%

3、三方库: pthread clone posix

4.、编写代码头文件: pthread.h

5、 编译代码加载库: gcc x.c -lpthread

扩:以lib开头 .so结尾的为动态库(在内核中),中间的pthread为库名。

(三)优缺点

优点:比多进程节省资源,可以共享变量。

缺点:1.线程和进程相比,稳定性,稍微差些

        2.线程的调试gdb,相对麻烦些,因为并发

二、线程与进程的区别(面问)

1、资源:

(1)线程比进程多了共享资源;

(2)线程又具有部分私有资源;

(3)进程间只有私有资源没有共享资源。

2、空间:

(1)进程空间独立(写时复制),不能直接通信;

(2)线程可以共享空间(栈区默认不共享),可以直接通信。

3、不同点:

(1)创建开销不一样,进程创建需要3G空间,线程创建只需要8M,线程并发度高于进

        程;

(2)进程变量不共享;

(3)进程切换(复杂)需要的缓存空间多,线程切换栈区,PC指针也发生变化;

(4)进程可申请到硬件资源;

4、稳定性差异:

(1)线程稳定性差(其中一个线程崩溃或严重异常,进程直接结束);

(2)项目任务复杂,用进程做;简单小任务,用线程做;

5、共同点:都能并发。

6、考虑用线程还是进程的两个准则:

(1)稳定性;

(2)看资源够不够用(够用用线程)。

三、多线程程序设计步骤

1、创建多线程(只要创建成功就启动了)

2、线程空间操作 (设计函数表达要干什么)

3、线程资源回收(栈区回收,默认进程结束栈区不释放)

注:

(1)进程中的第一个线程为主线程,主线程有且仅有一个,(主线程号和进程号一

致)其它次线程为平级关系;

(2)主线程不需创建,运行a.out就出来了,后调用函数创建其他空间,在进程空间新

开栈区(8M)。

四、线程的创建(相关函数)

1、pthread_create()

(1)函数原型:

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

(2)功能:该函数可以创建指定的一个线程。

(3)参数:

  thread 线程id,需要实现定义并由该函数返回。

  attr   线程属性,一般是NULL,表示默认属性。

  start_routine 指向指针函数的函数指针, 本质上是一个函数的名称即可。称为th 回调

  函数,是线程的执行空间。

(4)返回值:成功 0;失败 错误码

(5)注意:

一次pthread_create执行只能创建一个线程。
每个进程至少有一个线程称为主线程。
主线程退出则所有创建的子线程都退出。
主线程必须有子线程同时运行才算多线程程序。
线程id是线程的唯一标识,是CPU维护的一组数字。
pstree 查看系统中多线程的对应关系。
多个子线程可以执行同一回调函数。

(6)示例

注:gcc编译命令:gcc 01pthread_create.c -lpthread

2、pthread_self()

(1)函数原型:pthread_t pthread_self(void);

(2)功能:获取当前线程的线程id(谁调就返回谁的id号)

(3)参数:无

(4) 返回值:成功 返回当前线程的线程id;  失败  -1

(5)用户层表示(ps命令)与内核层表示(数字大)不同,使用内核层表示

        用户层查看(man ps     ps-eLf)

(6)示例:

3、扩展:strerror()

(1)报错函数,用于线程报错(多为极端情况使用),也可调用perror()函数(大部分

        情况使用)

(2)strerror(errno)中errno为错误码

五、线程的退出

1、pthread_exit()

自行退出 = =自杀 ==子线程自己退出

(1)函数原型:void pthread_exit(void *retval); exit return p;

(2)功能:子线程自行退出

(3)参数: retval 线程退出时候的返回状态,临死遗言。

(4)返回值:无

(5)示例:

2、pthread_cancel()

强制退出 ==他杀 ==主线程结束子线程

(1)函数原型:int pthread_cancel(pthread_t thread);

(2)功能:请求结束一个线程

(3)参数:thread 请求结束一个线程tid

(4)返回值:成功 0;失败 -1

(5)示例:

六、线程的回收

(一)线程的回收机制

1、不同与进程没有孤儿线程和僵尸线程;

2、主线程结束任意生成的子线程都会结束;

3、子线程的结束不会影响主线程的运行。

(二)线程回收相关函数 pthread_join()

1、函数原型:int pthread_join(pthread_t thread, void **retval);

2、功能:通过该函数可以将指定的线程资源回收(线程结束栈区默认没被释放),

                该函数具有 阻塞等待功能,如果指定的线程没有结束,则回收线程会阻塞。

3、 参数:thread  要回收的子线程tid
                  retval  要回收的子线程返回值/状态;ptread_exit(值);

                   二级指针改变指针的指向
 4、返回值:成功 0;   失败 -1

5、扩展:Tcb块(线程控制块)

                (进程中有10个线程,pcb块中就有10个tcb块)

6、次线程返回的不能为局部变量(解决办法:加static,malloc申请(+free()))

7、示例:

(1)pthread_join(NULL);

(2)void* ret;

        pthread_join(tid,&ret);

8、练习:

(三)次线程的回收策略

1、如果预估子线程可以有限范围内结束则正常用pthread_join等待回收;

2、如果预估子线程可能休眠或者阻塞则等待一定时间后强制回收;

3、如果子线程已知必须长时间运行则,不再回收其资源。

七、线程的参数,返回值

1、传参数整数示例:

传参目的:降低程序耦合性(传参>定义全局变量)

2、结构体示例:

#include <stdio.h>               // 包含标准输入输出头文件
#include <stdlib.h>              // 包含标准库头文件(含内存分配、atoi等函数)
#include <string.h>              // 包含字符串操作头文件(如strcat、strlen等)
#include <unistd.h>              // 包含UNIX系统相关头文件(此处可能未实际使用)
#include <pthread.h>             // 包含POSIX线程头文件,用于多线程编程
 
typedef struct                  // 定义结构体类型PER
{
char name[50];              // 姓名,字符数组长度50
int age;                    // 年龄,整数类型
char addr[50];              // 地址,字符数组长度50
} PER;                          // 结构体别名PER
 
void* th(void* arg)             // 线程函数,返回值和参数均为void指针
{
PER per = (PER*)arg;       // 将参数强制转换为PER结构体指针
printf("th:%s %d %s\n", per->name, per->age, per->addr);  // 打印线程中结构体信息
strcat(per->name, "1");     // 向姓名后拼接字符串"1"(修改原数据)
return per;                 // 返回结构体指针(作为线程返回值)
}
 
int main(int argc, char argv)  // 主函数,argc为参数个数,argv为参数数组
{
PER per;                    // 定义PER结构体变量per
bzero(&per, sizeof(per));   // 用bzero函数将per内存块清零(初始化)
printf("pls name:");        // 提示输入姓名
fgets(per.name, sizeof(per.name), stdin);  // 读取输入到name数组,含换行符
per.name[strlen(per.name)-1] = '\0';  // 删除末尾换行符(替换为字符串结束符)
char buf[5] = {0};          // 定义缓冲区,用于临时存储年龄输入
printf("pls age:");         // 提示输入年龄
fgets(buf, sizeof(buf), stdin);  // 读取输入到buf(最多4个字符+终止符)
per.age = atoi(buf);         // 将buf字符串转换为整数赋给age
printf("pls addr:");        // 提示输入地址
fgets(per.addr, sizeof(per.addr), stdin);  // 读取输入到addr数组,含换行符
per.addr[strlen(per.addr)-1] = '\0';  // 删除addr末尾换行符
pthread_t tid;              // 定义线程ID变量tid
pthread_create(&tid, NULL, th, &per);  // 创建线程,传入per地址作为参数
void ret = NULL;           // 定义线程返回值存储变量
pthread_join(tid, &ret);    // 等待线程结束,获取返回值
// 打印主线程中获取的线程返回值(结构体信息)
printf("join ret :%s %d %s\n", ((PER)ret)->name, ((PER*)ret)->age, ((PER*)ret)->addr);
system("pause");            // 调用系统命令pause(Windows环境暂停程序)
return 0;                   // 主函数正常退出
}

八、分离属性

目的:线程消亡,自动回收空间。

1、pthread_attr_init()

(1)函数原型:int pthread_attr_init(pthread_attr_t *attr);

(2)功能:初始化一个attr的变量

(3)参数:attr,需要变量来接受初始值

(4)返回值:0 成功,非0 错误

2、pthread_attr_destroy()

(1)函数原型:int pthread_attr_destroy(pthread_attr_t *attr);

(2)功能:销毁attr变量

(3)参数:attr,属性变量

(4)返回值:0 成功,非0 错误

3、pthread_attr_setdetachstate()

(1)函数原型: int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

(2)功能:把一个线程设置成相应的属性

(3)参数:attr,属性变量,有init函数初始化他。
                    detachstate:有2个可选值,
                    PTHREAD_CREATE_DETACHED

4、pthread_deatch()

(1)函数原型:int pthread_deatch(pthread_t thread);

(2)功能:设置分离属性

(3)参数:线程id号,填自己的id

线程清理函数

5、pthread_cleanup_push()

(1)函数原型:void pthread_cleanup_push(void (*routine)(void *), void *arg);

(2)功能:注册一个线程清理函数

(3)参数:routine,线程清理函数的入口
                    arg,清理函数的参数。

(4)返回值:无

6、pthread_cleanup_pop()

(1)函数原型:void pthread_cleanup_pop(int execute);

(2)功能:调用清理函数

(3)参数:execute,非0 执行清理函数
                                     0 ,不执行清理

(4)返回值:无

注:pthread_cleanup_push与pthread_cleanup_pop函数需成对出现;

7、注:

进程可以启动线程,线程也可启动进程(进程中的多线程变为单线程)

8、示例

(1)在循环中批量创建分离态线程,并统计成功创建的线程数量:

#include <pthread.h>               // 包含POSIX线程头文件,用于多线程操作
#include <stdio.h>                 // 包含标准输入输出头文件
#include <stdlib.h>                // 包含标准库头文件(含内存分配等函数)
#include <unistd.h>                // 包含UNIX系统相关头文件(此处可能未实际使用)
 
void* th(void* arg)               // 线程函数,返回值和参数均为void*指针
{
pthread_detach(pthread_self()); // 将当前线程设置为分离态,线程结束后系统自动回收资源
return NULL;                    // 线程执行完毕,返回空指针
}
 
int main(int argc, char** argv)   // 主函数,argc为参数个数,argv为参数数组
{
int i = 0;                    // 循环计数器
pthread_t tid;                // 存储线程ID的变量
for (i = 0; i < 50000; i++)   // 循环创建50000个线程
{
int ret = pthread_create(&tid, NULL, th, NULL); // 创建线程,传入线程函数th,无参数
if (ret != 0)             // 检查线程创建是否失败(非0表示失败)
{
break;                // 失败则退出循环
}
printf("%d\n", i);        // 打印当前创建的线程序号
}
system("pause");              // 调用系统命令pause(Windows环境暂停程序,防止退出)
return 0;                     // 主函数正常退出
}

(2)利用线程清理处理机制确保动态分配内存的正确释放:

#include <pthread.h>               // 包含POSIX线程相关头文件(线程创建、清理等)
#include <stdio.h>                 // 包含标准输入输出头文件(printf等)
#include <stdlib.h>                // 包含标准库头文件(malloc、free等内存操作)
#include <unistd.h>                // 包含UNIX系统相关头文件(此处未实际使用)
#include <string.h>                // 包含字符串操作头文件(strcpy等)
 
void clean(void arg)             // 线程清理处理函数,参数为void指针
{
printf("this clean %s\n", (char*)arg);  // 打印清理信息,输出传入的字符串
free(arg);                              // 释放动态分配的内存,避免泄漏
}
 
void* th(void* arg)               // 线程执行函数,返回值和参数均为void指针
{
strcpy((char )arg, "hello world\n");  // 将字符串复制到传入的内存地址(修改arg指向的数据)
printf("th,strcpy over\n");            // 打印线程内操作完成的提示
return NULL;                            // 线程执行完毕,返回空指针
}
 
int main(int argc, char *argv)   // 主函数,argc为参数个数,argv为参数数组
{
pthread_t tid;                // 定义线程ID变量,用于存储创建的线程标识
char p = malloc(50);         // 动态分配50字节内存,返回指针p指向该内存块
pthread_cleanup_push(clean, p);  // 注册线程清理函数clean,参数为p(内存地址)
pthread_create(&tid, NULL, th, p);  // 创建线程,传入线程函数th和参数p(内存地址)
pthread_join(tid, NULL);      // 阻塞等待线程tid结束,回收其资源
printf("before pop\n");       // 打印提示,表示即将执行清理函数弹出操作
pthread_cleanup_pop(1);       // 弹出清理函数,并执行(参数1表示执行清理函数)
system("pause");              // 调用系统命令pause(Windows环境暂停程序)
return 0;                     // 主函数正常退出
}

http://www.xdnf.cn/news/9389.html

相关文章:

  • 打破边界 智启新篇 新一代质检LIMS系统的演进蓝图
  • QGis实现geoserver上的样式展示(方便样式编辑)
  • ShardingSphere-读写分离
  • leetcode0611. 有效三角形的个数-medium
  • ROS2学习(14)------ ROS2Launch 多节点启动与配置脚本
  • 基于stm32的 永磁同步电机二电平驱动控制系统设计
  • OpenKylin文件管理器界面层级切换问题
  • 多相电机驱动控制学习(1)——基于双dq坐标系的六相/双三相PMSM驱动控制
  • ABC 377
  • 互联网医疗问诊APP原型设计:12个实战案例解析
  • Workflow
  • 如何合理选择智能外呼机器人:多维评估
  • SAP-ABAP:SAP的DMS根据物料号获取附件详解
  • 网络通信的基石:深入理解帧与报文
  • [BUG记录]0X10 会话切换服务响应NRC 0x10
  • <<运算符重载 和 c_str() 的区别和联系
  • TF 卡和 NM 卡有何区别?
  • openinstall支持豆瓣广告监测,赋能品牌深挖社交流量
  • Baklib知识中台体系构建与应用解析
  • 比较转录组-油料作物-文献精读133
  • Jenkins实践(10):pipeline构建历史展示包名和各阶段间传递参数
  • 【深度学习新浪潮】智能眼镜关键技术拆解(简要版)
  • 什么是 BOM 表,如何通过 BOM 表做好生产管理
  • git 删除某次commit并 推送到 origin
  • 安装 LCMS-8060 三重四级杆配件的详细步骤和要点
  • JavaSE核心知识点04工具04-03(Maven)
  • 简单产品图生成器v1(自己写的)
  • 散货拼柜业务有哪些管理难题?易境通散货拼柜系统如何协同化管理?
  • IPsec协议
  • Codeforces Round 1027 (Div. 3)