【系统编程】线程基础

news2024/11/19 7:30:36
  • (꒪ꇴ꒪ ),Hello我是祐言QAQ
  • 我的博客主页:C/C++语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍
  • 快上🚘,一起学习,让我们成为一个强大的攻城狮!
  • 送给自己和读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!
  • 作者水平很有限,如果发现错误,请在评论区指正,感谢🙏



        在多任务操作系统中,线程是操作系统中的基本执行单元,可以看作是一个进程内的子任务,与同一进程的其他线程共享相同的内存空间和资源。线程可以并发地执行使得程序能够更有效地利用多核处理器和资源。本篇博客将详细介绍线程的基础知识,包括线程的引入,进程和线程的区别,线程的资源,以及线程API和线程属性。

一、线程基础


1.1 引入线程


        在传统的操作系统中,进程是操作系统分配资源和调度的基本单位。然而,对于很多需要并发执行的任务,使用进程可能会导致资源的浪费。这是因为每个进程都有自己的地址空间,进程间的切换需要保存和恢复大量的上下文信息,这使得进程的创建和切换成本相当高。

        线程,也称为轻量级进程,是在一个进程内部并发执行的单元。相比于进程,线程有更小的上下文切换开销,因此更适合于高并发的场景。

1.2 进程和线程的区别


(1)资源拥有

        进程是资源拥有的单位,包括内存空间,文件描述符,信号处理函数等。相比之下,线程只拥有一小部分资源,如执行栈和自己的寄存器值,其他资源如内存空间,文件描述符等都与其所在的进程共享。

(2)调度和切换

        进程是操作系统调度的基本单位,进程间的切换需要交换很多上下文信息,开销较大。线程作为轻量级进程,线程间的切换只需要交换很少的上下文信息,开销较小

(3)通信方式

        进程间通信需要使用IPC(进程间通信)机制,如管道,消息队列,共享内存等。线程间通信更为方便,可以直接通过读写同一进程内的数据进行通信

1.3 线程的资源


        线程拥有自己的一些资源,如执行栈寄存器值(包括程序计数器),线程本地存储,以及线程的属性和优先级等。其他的资源,如内存空间,文件描述符,信号处理函数等,都与其所在的进程共享。

二、线程API


        在C语言中,线程的操作主要通过POSIX线程(pthread)库进行。以下是一些常用的线程API(程序编程接口)。这些线程相关函数不属于标准C库,编译多线程代码的时候,需要链接线程库 pthread,但Ubuntu版本到22时或更高就可以省略该操作。

2.1 线程创建


        线程的创建主要通过 pthread_create() 函数进行。这个函数会创建一个新的线程,并开始执行指定的函数。

#include <pthread.h>
        int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数:
    pthread_t *thread,                       //创建成功的时候得到的线程ID
    const pthread_attr_t *attr,           //线程属性 
    void *(*start_routine) (void *),     //线程任务函数
    void *arg                                     //线程任务函数需要的参数
返回值:

        成功返回0,失败返回错误码

        一个最简单的创建线程函数代码:

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

void* myThreadFunc(void* arg) {
    sleep(5);// 线程的执行函数
    return NULL;
}


int main() {
    pthread_t threadId;
    if (pthread_create(&threadId, NULL, myThreadFunc, NULL)) {
        // 处理错误
    }
    return 0;
}


2.2 接合线程(线程回收)


        当一个线程结束时,需要通过 pthread_join() 函数来接合(回收)线程,否则线程可能会变成僵尸线程。 pthread_join() 函数还可以获取线程的返回值。

int pthread_join(pthread_t thread, void **retval);
int pthread_tryjoin_np(pthread_t thread, void **retval);
参数:
    thread         线程 TID
    retval          储存线程退出值的内存的指针
返回值:
            成功  0
            失败  errno

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

void* myThreadFunc(void* arg) {
    sleep(5);// 线程的执行函数
    return NULL;
}

int main() {
    pthread_t threadId;
    if (pthread_create(&threadId, NULL, myThreadFunc, NULL)) {
        // 处理错误
    }
    if (pthread_join(threadId, NULL)) {
        // 处理错误
    }
    return 0;
}

注意:

        (1)如果线程退出时没有退出值,那么 retval 可以指定为 NULL;

        (2)pthread_join( )指定的线程如果尚在运行,那么他将会阻塞等待;

        (3)pthread_tryjoin_np( )指定的线程如果尚在运行,那么他将会立即出错返回。


2.3 子线程主动退出函数


        线程可以通过 pthread_exit() 函数来主动结束自己的执行,并返回一个值。

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

void* myThreadFunc(void* arg) {
    sleep(5);// 线程的执行函数
    return NULL;
}

int main() {
    pthread_t threadId;
    if (pthread_create(&threadId, NULL, myThreadFunc, NULL)) {
        // 处理错误
    }
    if (pthread_join(threadId, NULL)) {
        // 处理错误
    }
    return 0;
}

注意:        

        由于局部变量在函数退出的时候会释放,如果线程函数需要返回数据,那么这个数据必须保证函数退出依然有效,所以我们建议,要么用静态数据,要么用堆空间。

        pthread_exit()在主线程里面,他只会退出主线程,但是进程不会退出。进程里面相关的资源也暂时不会释放,等到其他线程依次退出,进程才会退出。

2.4 被动退出(将某个线程杀死)


        我们可以通过 pthread_cancel ()函数来结束一个线程的执行。

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

void* myThreadFunc(void* arg) {
    sleep(5);// 线程的执行函数
    return NULL;
}
int main() {
    pthread_t threadId;
    if (pthread_create(&threadId, NULL, myThreadFunc, NULL)) {
        // 处理错误
    }
    pthread_cancel(threadId);
    if (pthread_join(threadId, NULL)) {
        // 处理错误
    }
    return 0;
}


三、 线程属性(了解)


        线程属性是线程的一些可配置的属性,如是否可连接,堆栈大小,调度策略等。

3.1 属性函数使用步骤


        (1)使用线程属性一般需要以下步骤:使用 pthread_attr_init ()初始化线程属性对象;
        (2) 使用 pthread_attr_setxxx 函数设置线程属性;
        (3)创建线程时,将线程属性对象传递给 pthread_create ()函数;
        (4)使用完线程属性后,使用 pthread_attr_destroy 销毁线程属性对象。

#include <pthread.h>

void* myThreadFunc(void* arg) {
    // 线程的执行函数
}

int main() {
    pthread_t threadId;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    // 设置线程属性
    if (pthread_create(&threadId, &attr, myThreadFunc, NULL)) {
        // 处理错误
    }
    pthread_attr_destroy(&attr);
    if (pthread_join(threadId, NULL)) {
        // 处理错误
    }
    return 0;
}


3.2 分离属性


        线程的分离属性决定一个线程结束后,是否自动释放其资源。一个分离的线程在结束后会自动释放其资源,而一个非分离的线程则需要通过 pthread_join ()函数来回收。

3.3 调度策略


        线程的调度策略决定了线程的运行顺序。常见的调度策略有FIFO(先进先出),RR(轮转)和其他,默认的调度策略是系统决定的。

        (1)设置和获取静态优先级

int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);
int pthread_attr_getschedparam(pthread_attr_t *attr,struct sched_param *param);
静态优先级:0 到 99
        0 为默认的非实时普通进程
        1-99 为实时进程,数值越大,优先级越高


        (2)设置和获取动态优先级

int nice(int inc);
动态优先级:-20 到 19
        动态优先级数值越大,优先级越低

3.4 线程栈空间和警戒区


        每个线程都有自己的栈空间,用于存放局部变量和函数调用的上下文。栈空间的大小可以通过线程属性来设置。警戒区是线程栈的一部分,用于防止栈溢出时覆盖其他内存区域的数据。

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t guardsize);
参数:
    attr                   线程属性变量
    stacksize         线程栈的大小(默认8M)
    guardsize        警戒区的大小(默认4Kb)

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

//线程任务函数
void *task(void *arg)
{
	printf("线程任务函数执行一个10秒钟的任务\n");
	sleep(10);
	printf("线程任务函数结束\n");
}

int main(int argc, char const *argv[])
{
	//定义一个线程属性变量
	pthread_attr_t attr;

	//初始化
	pthread_attr_init(&attr);

	//获取栈大小
	size_t stacksize;
	pthread_attr_getstacksize(&attr, &stacksize);
	printf("栈大小:%ld\n", stacksize);

	//获取警戒区大小
	size_t guardsize;
	pthread_attr_getguardsize(&attr, &guardsize);
	printf("警戒区大小:%ld\n", guardsize);
	
	//创建线程
	pthread_t pid;
	pthread_create(&pid, &attr, task, NULL);
	//保证线程任务函数执行完毕,进程在退出

	pthread_join(pid, NULL);
	return 0;
}

3.5 获取、设置线程的取消类型和状态

pthread_setcancelstate(int state, int *oldstate);

用于设置线程的取消状态。它有两个参数:

        state:用于设置取消状态,可以是 PTHREAD_CANCEL_ENABLE(使能取消请求)或 PTHREAD_CANCEL_DISABLE(关闭取消请求)。
        oldstate:一个指针,用于存储之前的取消状态。
pthread_setcanceltype(int type, int *oldtype);

用于设置线程的取消类型。它有两个参数:

        type:用于设置取消类型,可以是 PTHREAD_CANCEL_DEFERRED(延时响应)或 PTHREAD_CANCEL_ASYNCHRONOUS(立即响应)。
        oldtype:一个指针,用于存储之前的取消类型。

        这两个函数通常在线程任务函数内部使用,用于控制线程的取消行为。

3.6 注册退出处理例程

void pthread_cleanup_push(void (*routine)(void *), void *arg);

用于注册退出处理例程,它有两个参数:

        routine:是一个函数指针,指向一个要在线程退出时执行的函数。
        arg:是传递给处理例程的参数。
void pthread_cleanup_pop(int execute);

用于取消之前使用 pthread_cleanup_push 注册的处理例程。它有一个参数:

        execute:如果该参数为非零值,表示要执行注册的处理例程,如果为零,则不执行。

        这两个函数必须成对使用,而且必须出现在同一层代码块中。它们允许在线程退出时执行清理工作,比如释放资源、关闭文件等操作。在你的示例中,handler1 ()函数就是一个退出处理例程,在子线程的任务函数中使用 pthread_cleanup_push ()注册,在需要时通过pthread_cleanup_pop ()执行。

        总的来说,这些函数允许你控制线程的取消行为和在线程退出时执行特定的清理操作,增加了多线程程序的可控性和健壮性,下面是一个例子:

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

//线程退出处理例程函数
void func(void *arg)
{
	printf("线程退出处理例程函数\n");//一般作用用来释放线程退出的时候,没有释放的资源
	free((int *)arg);
}

//线程任务函数
void *task(void *arg)
{
	int *p = malloc(12);
	//压栈线程退出处理例程函数
	pthread_cleanup_push(func, p);

	int a = 5;
	while(a--)
	{
		printf("线程执行1秒钟的任务\n");
		sleep(1);
	}
	
	printf("线程结束运行\n");

	//弹栈线程退出处理例程函数
	pthread_cleanup_pop(-1);

}


int main(int argc, char const *argv[])
{
	printf("主线程开始运行\n");

	pthread_t pid;
	pthread_create(&pid, NULL, task, NULL);



	sleep(3);
	pthread_cancel(pid);//取消请求
	sleep(5);


	return 0;
}

        更多C/C++语言Linux系统数据结构ARM板实战相关文章,关注专栏:

   手撕C语言

            玩转linux

                    脚踢数据结构

                            系统、网络编程

                                     探索C++

                                             6818(ARM)开发板实战

📢写在最后

  • 今天的分享就到这啦~
  • 觉得博主写的还不错的烦劳 一键三连喔~
  • 🎉🎉🎉感谢关注🎉🎉🎉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/962173.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实现数据加密传输,保障数据安全的智能网关

在工业自动化领域&#xff0c;不同的设备和系统通常采用不同的通信协议进行数据交换。为了实现不同设备之间的无缝连接和数据共享&#xff0c;协议转换网关成为了一种关键的工具。BL110是一款功能强大的协议转换网关&#xff0c;能够实现多种协议之间的转换&#xff0c;包括PLC…

scratch绘制同心圆 2023年5月中国电子学会图形化编程 少儿编程 scratch编程等级考试四级真题和答案解析

目录 scratch绘制同心圆 一、题目要求 1、准备工作 2、功能实现 二、案例分析 <

【仿牛客论坛java项目】第五章 Kafka,构建TB级异步消息系统:阻塞队列、Kafka入门、Spring整合Kafka、发送系统通知、显示系统通知

这里写自定义目录标题 一、阻塞队列简单的阻塞队列测试案例总结阻塞队列 二、Kafka入门1、基础知识Kafka术语消息队列实现方式两种 2、配置3、启动全部命令启动 zookeeper 服务器再启动 kafka 服务器创建Topic关闭 4、总结Kafka的特点Kafka的术语 三、 Spring整合Kafka导入依赖…

ArcGIS将两个相同范围但不同比例或位置的矢量数据移动到相同位置

有两个市图层&#xff0c;一个是正确经纬度的市行政范围图层&#xff0c;另一个是其他软件导出获取的不正确经纬度信息或缺失信息。 如果单纯的依靠移动图层&#xff0c;使不正确的移动到正确位置需要很久。尝试定义投影等也不能解决。 使用ArcMap 的空间校正工具条&#xff…

基于单片机的万年历温度无线传输控制系统系统

一、系统方案 本设计采用DS1302采集年月日时分秒&#xff0c;DS18B20采集温度值&#xff0c;按键设置温度报警上下限&#xff0c;实际测量温度低于下限或高于上限&#xff0c;蜂鸣器报警&#xff0c;同时将测量温度上传到蓝牙助手。 二、硬件设计 原理图如下&#xff1a; 三…

JavaScript基础语法03——JS注释、结束符

哈喽&#xff0c;大家好&#xff0c;我是雷工&#xff01; 今天继续学习JavaScript基础语法知识&#xff0c;注释和结束符&#xff0c;以下为学习笔记。 一、JavaScript注释 JavaScript注释有什么作用&#xff1f; JavaScript注释可以提高代码的可读性&#xff0c;能够帮助像…

E5071C是德科技网络分析仪

描述 E5071C网络分析仪提供同类产品中最高的RF性能和最快的速度&#xff0c;具有宽频率范围和多功能。E5071C是制造和R&D工程师评估频率范围高达20 GHz的RF元件和电路的理想解决方案。特点: 宽动态范围:测试端口的动态范围> 123 dB(典型值)快速测量速度:41毫秒全2端口…

FIR滤波器算法

FIR&#xff08;Finite Impulse Response&#xff09;滤波器是一种基于有限长输入信号的数字滤波器&#xff0c;常用于去除数字信号中的噪声和干扰。其特点是具有线性相位响应&#xff0c;可以实现任意的频率响应和通带、阻带等设计参数。 FIR滤波器的数学模型描述如下&#x…

elasticsearch分析插件 安装analysis-ik

首先下载安装es 和 插件 &#xff0c;注意 两者的版本要保持一致,如果要用到kibana 则三者保持一致 ik&#xff1a;https://github.com/medcl/elasticsearch-analysis-ik/releases es/kibana&#xff1a;https://www.elastic.co/cn/downloads/past-releases/ 然后在 es— elast…

02_块元素和行内元素的使用

一、HTML块元素和行内元素的使用 1、块元素: div标签 定义和用法&#xff1a; 标签块元素,表示一块内容,div标签可以把文档分割为独立的、不同的部分可以使用css设置宽高默认是占用一整快 例如: <html><body><!-- 块元素:div标签 --><div style"he…

Unity3D下如何采集camera场景数据并推送RTMP服务?

Unity3D使用场景 Unity3D是非常流行的游戏开发引擎&#xff0c;可以创建各种类型的3D和2D游戏或其他互动应用程序。常见使用场景如下&#xff1a; 游戏开发&#xff1a;Unity3D是一个广泛用于游戏开发的环境&#xff0c;适用于创建各种类型的游戏&#xff0c;包括动作游戏、角…

学信息系统项目管理师第4版系列04_组织通用治理

1. 组织治理 1.1. 协调组织利益相关者之间关系的一种制度安排&#xff0c;目标是为了确保组织的高效决策&#xff0c;实现利益相关者之间的利益均衡&#xff0c;提高组织的绩效&#xff0c;确保组织运行的可持续发展 2. 组织战略 2.1. 组织高质量发展的总体谋略 2.2. 组织相…

iPhone 14 Plus与iPhone 14 Pro:你应该买哪一款

又到了iPhone季,这意味着你可能会在几种不同的机型之间左右为难,无法决定买哪一款。更令人困惑的是,苹果推出的iPhone变体——iPhone 14 Plus,只比老款iPhone 14 Pro低100美元。 有这么多选择,你可能想知道哪款iPhone最适合你。你应该买一部大屏幕的iPhone 14 Plus并节省…

ArmSoM-W3 DDR压力测试

1. 简介 专栏总目录 ArmSoM团队在产品量产之前都会对产品做几次专业化的功能测试以及性能压力测试&#xff0c;以此来保证产品的质量以及稳定性 优秀的产品都要进行多次全方位的功能测试以及性能压力测试才能够经得起市场的检验 2. 环境介绍 硬件环境&#xff1a; ArmSoM-W…

Matlab图像处理-灰度分段线性变换

灰度分段线性变换 如数学涵义的分段一般&#xff0c;分段线性变换就是将图像不同的灰度范围进行不同的线性灰度处理。其表达式可表示如下&#xff1a; 灰度分段线性变换可根据需求突出增强目标区域&#xff0c;而不增强非目标区间&#xff0c;达到特定的显示效果。 示例程序 …

深度学习推荐系统(四)WideDeep模型及其在Criteo数据集上的应用

深度学习推荐系统(四)Wide&Deep模型及其在Criteo数据集上的应用 在2016年&#xff0c; 随着微软的Deep Crossing&#xff0c; 谷歌的Wide&Deep以及FNN、PNN等一大批优秀的深度学习模型被提出&#xff0c; 推荐系统全面进入了深度学习时代&#xff0c; 时至今日&#x…

原生js实现轮播图及无缝滚动

我这里主要说轮播图和无缝滚动的实现思路&#xff0c;就采用最简单的轮播图了&#xff0c;当然实现的思路有很多种&#xff0c;我这也只是其中一种。 简单轮播图的大概结构是这样的&#xff0c;中间是图片&#xff0c;二边是箭头可以用来切换图片&#xff0c;下面的小圆点也可以…

【广州华锐互动】VR全景工厂虚拟导览,虚拟现实技术提升企业数字化信息管理水平

随着工业4.0的到来&#xff0c;VR工厂全景制作成为了越来越多工业企业的选择。传统的工厂管理方式往往存在诸多问题&#xff0c;如信息不对称、安全隐患等。为了解决这些问题&#xff0c;VR工厂全景制作应运而生&#xff0c;它通过结合虚拟现实现实技术和数据采集技术&#xff…

PostGIS空间数据中基础常用函数介绍

目录 前言 一、基础数据 1、数据结构准备 2、基础数据构造 二、常用空间函数 1、st_srid 获取空间对象SRID 2、st_asgeojson geojson转换 3、st_aswkt wkt支持 4、st_area 面积计算 5、ST_Buffer 缓冲区 6、其它函数 总结 前言 近些年&#xff0c;面向GIS的应用如雨后…

画图工具draw.io UML图 使用

点击下载 git 下载地址&#xff1a;https://github.com/jgraph/drawio-desktop/releases 在线版本&#xff08;推荐&#xff0c;可以储存&#xff0c;可以共享&#xff09;&#xff1a;https://app.diagrams.net/ 流程图相关