0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

函数的可重入与线程安全有什么关系

汽车电子技术 来源:宅学部落 作者: 王利涛 2023-02-17 09:39 次阅读

嵌入式裸机时代,也就是无OS时代,我们在裸机环境下编写C语言程序非常简单,实现一个函数,然后将函数接口API提供给其它模块调用就可以了。比如下面的函数,我们实现一个sum函数,用来求两个数的和:

图片

但是在一个运行OS的多任务环境中,我们在编写sum函数时就要注意一些细节了:我们编写的sum函数可能会被多个任务调用,而且可能在sum函数执行的过程被打断,接着在另一个任务中再次调用sum函数。而在上面的sum函数实现中,我们定义了一个静态变量sum用来保存两个数相加的临时结果,静态变量是保存到数据段中的,大家可以想一想,在一个任务A中正在执行sum(1,2)函数中的第4行,此时任务被打断挂起,接着运行任务B,在任务B中接着执行sum(10,20)函数,执行结束后接着运行任务A,A获得CPU控制权后继续运行sum(1,2)的第5行,此时sum(1,2)的返回结果就变成了30,而不是正确结果3。

在一个多任务环境中,如果一个函数可以重复并发调用,而且多次调用并不会影响函数的运行结果,那么这个函数是可重入的,我们称这个函数为:可重入函数。在上面的sum函数实现中,当其被多次并发调用时,函数的运行结果并不确定,我们称其为不可重入函数。

我们如何去判定一个函数是可重入的,还是不可重入的呢?很简单,当一个函数满足下面任一条件,那么这个函数就是不可重入函数。

  • 函数内部使用了全局变量
  • 函数内部使用了静态局部变量
  • 函数返回值为全局变量或静态变量
  • 函数内部使用了malloc/free函数
  • 函数北部使用了标准I/O函数
  • 函数内部调用其它不可重入函数

不可重入函数在一个多任务环境中不能被多次并发调用,如果一个函数可能被多次调用,那么我们设计这个函数时尽量要将其设计为可重入函数。

  • 不使用/返回静态变量、全局变量
  • 不使用标准I/O函数
  • 不使用malloc/free函数
  • 不调用不可重入函数

在函数设计时,只要注意上面的原则,那么我们就可以将一个函数设计为可重入函数,可重入函数在多任务环境下可以被多次并发调用,是线程安全的,程序员可以放心大胆地调用。

理想很丰满,现实很骨干。我们在编程中如果说不用malloc/free、全局变量,那是不现实的。只要我们使用了这些全局变量,静态变量,那么函数就变成不可重入了,在多任务环境下使用这个函数就变得线程不安全了,那怎么办呢?

方法还是有的,一个函数之所以变得不可重入,就是因为函数内有一些资源是全局共享的,在多任务环境下多次并发调用该函数时可能会破坏掉这些共享的全局资源。我们如果把这些资源在访问的时候保护起来,不让其它任务访问(即互斥访问),即同一时刻只允许一个进程访问就安全了。这些被保护的资源我们称为临界资源,访问这些临界资源的代码段,我们称之为临界区。临界区的访问方式为互斥访问,即同一时刻只允许一个进程访问。

临界区的实现方式有很多种,不同的操作系统可能会提供不同的实现方式。我们可以通过下面的操作原语来实现一个临界区:

  • EnterCriticalSection()
  • LeaveCriticalSection()

不同的操作系统,具体的实现手段可能不一样,常见的方法有:关中断;实现互斥访问,比如通过信号量、互斥量、自旋锁等实现,甚至原子操作等。比如在uc/os操作系统中,我们使用关中断的方式来实现临界区,确保函数的线程安全。

图片

而在linux/windows操作系统中,我们通常使用锁机制来实现临界区:

图片

在一个不可重入函数中,通过临界区来实现共享全局资源的互斥访问,那么在多任务环境下调用这个函数也就变得安全了,也就是说这个不可重入函数是线程安全的。

通过上面的分析,我们可以得出下面的结论:一个函数如果是可重入函数,那么这个函数是线程安全的,其它进程线程都可以对这个函数并发访问,并不会影响函数的运行结果。如果一个函数是不可重入函数,我们通过临界区设计对共享全局资源进行互斥访问,也可以让这个函数变得线程安全,其它进程线程也可以放心调用。由此,我们得出线程安全与可重入之间的关系如下:

图片

也就是说,一个可重入函数肯定是线程安全的,而线程安全函数并不一定是可重入函数,不可重入函数也有可能是线程安全的,比如我们常见的malloc函数,就是不可重入函数,但是是线程安全的,为什么呢?

通过《C语言嵌入式Linux高级编程》课程学习,我们已经知道,对于我们使用malloc/free申请释放的内存,glibc在用户空间实现了一个内存管理器,将各个大小的内存块链成多个全局链表进行管理。

图片

当我们使用malloc/free申请释放内存时,如果申请/释放的内存块大小符合规定,一般都是直接对这些全局链表进行操作、避免多次系统调用进入内核态,减少系统开销。因为malloc/free函数对全局链表进行了操作,所以malloc/free是不可重入函数。在访问这些全局链表时,我们需要通过锁机制加以保护,每次malloc/free操作全局链表时,其它地方就被互斥访问了,只有当malloc/free操作全局链表完成退出,其它地方的malloc/free才能对这个全局链表进行访问。

通过上面的分析,我们可以看到:malloc/free虽然是不可重入函数,但是通过加锁对共享全局资源的互斥访问,也就变得线程安全了,在多任务环境下,每个进程都可以放心大胆地调用它:因为malloc虽然是不可重入函数,但它是线程安全的。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4983

    文章

    18295

    浏览量

    288594
  • C语言
    +关注

    关注

    180

    文章

    7534

    浏览量

    128834
  • 函数
    +关注

    关注

    3

    文章

    3904

    浏览量

    61311
收藏 人收藏

    评论

    相关推荐

    Keil C51处理科重入函数问题的探讨

    在程序设计中,变量具体可以分为四种类型: 全局变量 ,静态全局变量,局部变量,静态局部变量。这几种变量类型对函数重入产生的重大的影响,因为不同的编译器采用不同的策略。 针对51的存储区有
    发表于 04-22 21:40

    调用非安全线程的dll的问题

    在调用非线程安全的dll时,是不是要选择在UI线程中运行?是不是还必须用不可重入的子VI封装一下?上述的两步是不是都要做?这些问题不是很清楚,还请各位大神指点一下
    发表于 03-14 21:13

    我想问如果我异步调用重入 参数是X80会怎么样

    这个问题 因为我感觉里面有的地方很怪异,我在这些线程里用了全局变量 存了这个重入的VI的停止事件引用在别的地方去调用 停止它 就有时候 感觉并不能停止掉 关闭工程文件时候说是有线程
    发表于 06-06 19:38

    用ERTM关闭全局中断来实现函数重入性有什么附加影响?

    在编程中,用ERTM关闭全局中断来实现函数重入性有什么附加影响?
    发表于 08-09 11:12

    请问ucosiii系统定义中断函数和裸机中的中断函数什么关系

    请问ucosiii中的系统定义中断函数和裸机中的中断函数什么关系,二者是怎么联系起来的????????比如ucos中断(void)BSP_IntHandlerEXTI1(void)和裸机中断EXTI1_IRQHandler()
    发表于 04-23 04:11

    重入函数相关资料推荐

    数码管点亮时间约为1~2ms。在数码管数字变化时,先熄灭再更新数据,称为消隐。using 0 是第0组寄存器;reentrant声明的函数重入函数
    发表于 01-11 07:37

    函数对FFT有什么影响?他们是什么关系

    函数对FFT有什么影响?他们是什么关系?在visualStudio软建中,要对音频信号进行FFT变换时,需要加窗函数进行控制,这是为什么?窗函数对FFT有什么影响?窗
    发表于 11-30 06:24

    Linux 多线程重入函数

    的相互影响,如果一个函数在多线程并发的环境中每次被调用产生的结果是不确定的,我们就说这个函数是"不可重入的"/"线程
    发表于 05-16 17:41 826次阅读

    重入函数与不可重入函数分析

    导致不可预料的后果。那么什么是可重入函数呢?所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。不可重入
    发表于 04-02 14:43 689次阅读

    51单片机的可重入函数有什么陷阱

    函数一旦定义为可重入, 参数就会通过堆栈传递。 不要忘记的是, 局部变量也会在堆栈上分配。 更不能忽略的是, 51的堆栈空间大小是在2^8以内的, 所以坚决不能在可重入函数的局部变量中
    发表于 08-20 17:31 0次下载
    51单片机的可<b class='flag-5'>重入</b><b class='flag-5'>函数</b>有什么陷阱

    重入和不可重入函数的详细资料和应用简介

    重入一般可以理解为一个函数在同时多次调用,例如操作系统在进程调度过程中,或者单片机、处理器等的中断的时候会发生重入的现象。一般浮点运算都是由专门的硬件来完成,举个例子假设有个硬件寄存器名字叫做FLOAT,用来计算和存放浮点数的中
    发表于 08-02 17:34 0次下载
    可<b class='flag-5'>重入</b>和不可<b class='flag-5'>重入</b><b class='flag-5'>函数</b>的详细资料和应用简介

    Linux中的可重入、异步信号安全线程安全

    下文是在看csapp的时候引发的一些思考,其实之前看anup的时候也有所了解,不过时间有点长了,所以有点忘记了,当再次在csapp看到这部分内容的时候有了更多的理解。 可重入函数 当一个被捕获的信号
    的头像 发表于 11-10 14:45 1197次阅读
    Linux中的可<b class='flag-5'>重入</b>、异步信号<b class='flag-5'>安全</b>和<b class='flag-5'>线程</b><b class='flag-5'>安全</b>

    为什么中断处理函数不能直接调用不可重入函数

    中断丢失和系统位置错误,这里直接导致嵌入式 linux 系统应用进程中的所有线程停掉,进而导致看门狗进程得不到喂狗,设备重启。 那什么是不可重入函数呢? 为什么中断处理函数不能直接调用
    的头像 发表于 02-17 09:33 5030次阅读

    如何使用CUDA使warp级编程安全有

      NVIDIA GPUs 以 SIMT (单指令,多线程)方式执行称为 warps 的线程组。许多 CUDA 程序通过利用 warp 执行来获得高性能。在这个博客中,我们将展示如何使用 CUDA 9 中引入的原语,使您的 warp 级编程
    的头像 发表于 04-28 16:09 2391次阅读
    如何使用CUDA使warp级编程<b class='flag-5'>安全有</b>效

    CPU的核心数和线程数有什么关系

    1 概念 1.1 背景 当看到以下一些名词,你是否感到过疑惑:他们之间到底有什么关系? CPU核心数、线程数、处理器数量、每个处理器的内核数量、处理器内核总数、逻辑核数… 在安装linux虚拟机
    的头像 发表于 11-24 16:22 833次阅读
    CPU的核心数和<b class='flag-5'>线程</b>数有<b class='flag-5'>什么关系</b>