线程安全函数,可重入函数,异步信号安全函数

线程安全函数,可重入函数,异步信号安全函数

线程安全

线程安全是一种适用于多线程代码的计算机编程概念。线程安全仅以确保所有线程正确运行并满足其设计规范而不会出现意外交互的方式操作共享数据结构。编写线程安全的函数有多种策略。

一个程序可以在共享地址空间中同时在多个线程中执行代码,其中每个线程都可以访问每个其他线程几乎所有内存。线程安全是一种特性,它允许代码在多线程环境中运行。

线程安全的层级

软件库可以提供某些线程安全保证。例如,并发读可能是线程安全的,但并发写入可能不是线程安全。使用此类库的程序是否是线程安全的取决于它是否以与这些保证一致的方式使用该库。

不同的服务商使用不同的线程安全术语:

  • 线程安全:当多个线程同时访问时,保证没有竞争条件。
  • 条件安全:不同的线程可以同时访问不同的对象,并且可以保护对共享数据的访问免受竞争条件的影响。
  • 非线程安全:不同的线程不应同时访问数据结构。

线程安全通常还包括防止或限制不同形式死锁风险的设计步骤,以及最大化并发性能的优化。然而,不能总是提供无死锁保证,因为死锁可能是由回调和违反独立库本事呢的架构分层引起的。

线程安全实现方法

下面讨论两类避免竞争条件以实现线程安全的方法。

第一类方法侧重于避免共享状态,包括

  • 可重入:将状态信息保存在每次执行的局部变量中,通常在栈上,而不是在静态或全局变量或其他非局部状态中
  • 线程局部存储:变量被本地化,以便每个线程都有自己的私有数据。这些变量跨子例程和其他代码边界保留它们的值并且是线程安全的,因为它们对于每个线程都是本地的,即使访问它们的代码可能由另一个线程同时执行
  • 不可变对象:对象的状态在构造后无法更改。这意味着仅共享只读数据和获得固有的线程安全性。然后可以通过创建新对象而不是修改现有现有对象的方式来实现可变(非常量)操作

第二类方法与同步相关,用于无法避免共享状态的情况

  • 互斥:对共享数据的访问确保在任何时候只有一个线程读取或写入共享数据的机制。使用互斥,需要深思熟虑,因为不正确的使用会导致死锁,活锁和资源匮乏等影响。
  • 原子操作:共享数据通过不能被其他线程中断的原子操作来访问。这通常需要特殊的机器语言指令。因为操作是原子的,因此无论其他线程如何访问,共享数据始终保持有效状态。

可重入

所谓可重入,常见的情况是,程序执行都某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入,此时如果foo()能够正确的运行,而且处理完成之后,之前暂停的foo()也能正确运行,则说明它是可重入的
在计算机中,如果多个调用可以安全的在单个处理器系统上并发运行,则计算机程序或子程序称为可重入的,其中一个可重入程序可以在其执行过程中被中断,然后在其先前的调用完成执行之前再次安全地被调用(重新进入)。中断可能由内部操作(例如跳转或调用)或外部操作(例如中断或信号)引起,与递归不同,其中新调用只能由内部调用引起。

可重入的定义来源于多道程序设计,其中控制流可能被中断并转移到中断处理程序或处理程序子程序中。当中断被触发时,处理程序使用的任何子程序都应该是可重入的。通常,可通过操作系统内核访问的子程序是不可重入的。因此,中断处理程序在它们可以执行的操作方面受到限制;例如,它们通常被限制访问文件系统,有时甚至不能分配内存。

可重入与多线程环境中的线程安全的定义不同。可重入程序可以实现线程安全,但是线程安全代码不一定都是可重入的

背景

重入性与幂等性不同,在幂等性中,函数可以被多次调用,但生成的输出与只调用一次时完全相同。一般而言,函数会根据某些输入数据生成输出数据。任何函数都可以随时访问共享数据,如果任何函数都可以更改数据(并且没有人记录这些更改),则无法向共享数据的人保证该数据与之前的任何时间相同。

数据有一个可以称为范围的特性,它描述了可以在程序中的哪个位置使用数据。数据范围是全局的(在任何函数的范围之外并且具有无限的范围)或局部的(每次调用函数时创建并在退出时销毁)。

本地数据不被任何程序共享,它不影响可重入。全局数据是在函数外部定义的,可以被多个函数访问,可以是全局变量(所有函数之间共享的数据)的形式,也可以是静态变量(同一函数的所有调用共享的数据)。

可重入性不同于线程安全,但与线程安全密切相关。一个函数可以是线程安全且不可重入的。例如,一个函数可以被一个互斥体包裹起来(避免了多线程环境中的问题),但是,如果在中断服务程序中使用该函数,它可能会饥饿等待中断服务程序释放互斥体。可重入是指只执行一个线程

可重入的准则

  • 可重入代码不可以包含任何静态变量以及全局非常量数据。可重入函数可以处理全局数据。例如,可重入中断处理程序可以获取要使用的硬件状态(例如,串行端口读取缓冲区),这不仅是全局的,而且是易失的。不建议使用静态变量和全局数据,因为在这些变量中只可以使用原子读-修改-写指令(在执行这样的指令期间不应该出现中断或者信号)。请注意,在C语言中,即使读取或者写入也不能保证是原子操作,它可能被拆分成多个读取或写入操作。
  • 可重入代码不可以修改自身:操作系统可能允许进程修改其代码,造成这种情况的原因有很多,但这会导致重入问题,因为下次执行代码可能不一致。然而,如果它驻留在自己独占的内存中修改自身。也就是说,如果每个调用使用不同的物理机器代码位置,在该位置上拥有原始代码的副本,即使它在该特定调用(线程)的执行期间修改自身,也不会影响其他调用。
  • 可重入代码不能调用不可重入的计算机程序

例子

  • 不可重入且非线程安全
int tmp;
void swap(int* x,int* y)
{
    tmp = *x;
    *x = *y;
    //hardware interrupt might invoke isr() here
    *y = tmp;
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}
  • 线程安全,不可重入
_Thread_local int tmp;
void swap(int* x, int* y)
{
    tmp = *x;
    *x = *y;
    //hardware interrupt might invoke isr() here
    *y = tmp;
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}
  • 可重入,非线程安全
int tmp;
void swap(int* x, int* y)
{
    //save global variable
    int s;
    s = tmp;
    
    tmp = *x;
    /* 
    if hardware interrupt occurs here then it will fail to keep the value of
    tmp. So this is also not a reentrant example
    */
    *x = *y;
    *y = tmp;	//hardware interrupt might invoke isr() here
    
    tmp = s; //restore global variable
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}
  • 可重入,线程安全
void swap(int* x, int* y)
{
    int tmp;
    tmp = *x;
    *x = *y;
    *y = tmp;	//hardware interrupt might invoke isr() here
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

异步信号安全函数

异步信号安全函数是一种可以从信号处理程序中安全调用的函数。许多函数不是异步信号安全的。特别是,从信号处理程序调用不可重入函数通常是不安全的。

在文件上执行缓冲I/O时,stdio函数必须维护一个静态分配的数据缓冲区以及记录数据量和缓冲区中当前位置的相关计数器和索引(指针)。假设主程序正在调用printf()之类的stdio函数,其中缓冲区和相关变量已部分更新。如果此时程序被同样调用的信号处理程序中断,那么对printf()函数的第二次调用将对不一致的数据进行操作,结果不可预测。

为了避免不安全函数的问题,有两种可能的选择

  • 确保(a)信号处理程序仅调用异步信号安全函数以及(b)信号处理程序本身对主程序中的全局变量是可重入的。
  • 当调用不安全的函数或对信号处理程序也访问的全局数据进行操作时,在主程序中阻止信号传递。

一般来说,一个函数是异步信号安全的,要么因为它是可重入,要么因为它对于信号是原子的,即,它的执行不能被信号处理程序中断

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信号处理程序是在应用程序中处理操作系统发送的信号函数异步安全函数是指在多线程或者信号处理上下文中可以安全调用的函数。在信号处理程序中,由于信号的突发性和不可预测性,需要使用异步安全函数来保证程序的正确性和稳定性。 异步安全函数具有以下特点: 1. 不会修改全局状态或者共享数据:由于信号处理程序可能在任何时间点中断应用程序的正常执行,因此异步安全函数应该避免修改全局状态或者共享数据,以免引发竞态条件或者数据不一致性。 2. 不会调用不可重入函数:不可重入函数是指在执行过程中使用了全局或者静态变量的函数,由于信号处理程序可能在同一时间点被多个线程调用,因此异步安全函数应该避免调用不可重入函数,以免产生竞态条件或者数据不一致性。 3. 只使用异步信号安全函数异步信号安全函数是指能够在信号处理上下文中安全调用的函数,这些函数通常是线程安全的,不会引发竞态条件或者数据不一致性。 常见的异步安全函数包括: - signal():用于设置信号处理程序。 - sigaction():用于设置信号处理程序,并提供更加灵活和可靠的信号处理方式。 - sigprocmask():用于操作进程的信号屏蔽字,可以设置在信号处理程序执行期间需要被屏蔽的信号集合。 - sigsuspend():用于挂起进程的执行,直到收到某个特定信号为止。 需要注意的是,在信号处理程序中只能调用异步安全函数,而不能调用非异步安全函数,否则可能会导致不可预料的行为或者错误。因此,在编写信号处理程序时,需要仔细选择调用的函数,并确保它们是异步安全的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值