可重入函数和线程安全的对比

线程安全

一般说来,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。 

要保证线程安全,重点是保护共享资源,如全局变量、静态局部变量。为了解释线程安全,可以模拟实现一个sleep函数。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void handler(int sig)//信号捕捉处理函数
{
	printf("i am handler, alarm ringing\n");
}
int mysleep(int timeout)
{

	struct sigaction act, oact;
	act.sa_handler = handler;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGALRM, &act, &oact);
	
	alarm(timeout);
	pause();
	
	int ret = alarm(0);//闹钟置空
	sigaction(SIGALRM, &oact, &act);
	return ret;//ret接收了上次闹钟剩余时间,若不等于0出错
}

这个函数有一个很隐蔽的错误,假设在设定闹钟之后,当前线程被切到后台等待,而这个线程的优先级非常低,当他恢复到前台时,

闹钟设定的信号已经发送,而线程没有收到,继续执行pause,此时永远不会被唤醒。

想要解决只要分四步:

1.屏蔽信号

2.设定闹钟

3.解除屏蔽

4.pause

但是,这个方法也有危险,在3、4步之间也有可能被切出去,只能把pause换成sigsuspend来解决。

理清了思路,把代码做如下修改

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void handler(int sig)
{
	printf("i am handler, alarm ringing\n");
}

int mysleep(int timeout)
{
	struct sigaction act, oact;
	act.sa_handler = handler;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGALRM, &act, &oact);

	sigset_t mask, omask, suspmask;
	sigemptyset(&mask);
	sigaddset(&mask, SIGALRM);
	sigprocmask(SIG_BLOCK, &mask, &omask);

	alarm(timeout);
	
	suspmask = omask;
	sigdelset(&suspmask, SIGALRM);
	sigsuspend(&suspmask);

	int ret = alarm(0);
	sigaction(SIGALRM, &oact, &act);
	sigprocmask(SIG_SETMASK, &omask, NULL);
	return ret;
}


可重入函数

一个函数被称作可重入的,当且仅当可以被多个执行流进入而保证不出错

可重入与线程安全并不等同。一般说来,可重入的函数一定是线程安全的,但反过来不一定成立

一个单链表插入函数分三步:创建node、node指向head指向的节点 、head指向node

当多个执行流进入这个函数时,就会出问题




<think>嗯,用户这次想明确区分可重入函数线程安全这两个容易混淆的概念。从引用内容看,用户已经查阅了相关资料,但需要更清晰的对比解释。用户可能是开发者或计算机专业学生,正在学习多线程编程的基础概念。 注意到引用[1][3]都强调可重入函数“不引用共享数据”的特性,而引用[2]详细讨论了线程本地存储的实现方式。用户真正需要的可能是实践层面的区分标准,比如如何判断某个函数是否可重入,以及在什么场景下选择哪种实现方式。 上次回答中我用表格对比了五个维度(定义、数据依赖、中断安全性、同步机制、效率),这次应该延续这个框架但更聚焦区别点。特别要澄清一个关键误解:线程安全函数不一定可重入(如有锁保护的全局变量操作),可重入函数也未必线程安全(如使用线程局部存储但未互斥访问系统资源)。 用户可能隐含的实际需求是:在开发中如何避免因混淆概念导致的bug。需要补充说明线程安全函数可能依赖锁同步,而纯粹的可重入函数通常通过栈变量或线程局部存储实现无锁编程——这点对高性能场景很重要。 引用[3]提到的“中断后数据不丢失”是可重入独有的特性,值得单独强调。最后考虑用文件操作的例子说明:可重入版本必须避免使用全局缓冲区,而线程安全版本可能用互斥锁保护共享文件句柄。 </think>理解可重入函数线程安全的区别是多线程编程的核心概念。以下是基于引用的清晰对比分析: ### 核心区别 | **维度** | **可重入函数** | **线程安全函数** | |-------------------|-------------------------------------------|------------------------------------------| | **定义** | 函数**在执行过程中被中断后再次调用**仍能正确运行 | 函数**被多个线程并发调用**时能保持行为正确 | | **数据依赖** | **禁止使用**任何静态/全局数据或共享资源 [^1][^3] | **允许使用**共享数据,但需通过锁等机制强制同步 | | **中断安全性** | ✅ 可被信号/中断打断后恢复执行而不破坏数据 [^3] | ❌ 不保证中断安全性 | | **同步机制** | 天然无锁,无需同步操作 [^1] | 通常依赖锁(互斥锁/信号量)等同步机制 | | **效率** | 更高(无锁开销)[^1] | 较低(同步机制引入开销) | ### 关键联系 1. **包含关系** 可重入函数线程安全函数 [^1] *原因*:可重入函数不依赖共享状态,天然满足多线程并发要求。 2. **转化限制** 依赖全局变量的线程不安全函数 → 必须重写为**可重入函数**才能实现线程安全(仅加锁无法解决重入问题)[^1] ### 实践案例对比 ```c // 可重入函数示例:仅用局部变量 int reentrant_add(int a, int b) { return a + b; // ✅ 无共享状态 } // 线程安全但不可重入:用锁保护全局变量 int global_counter = 0; pthread_mutex_t lock; int thread_safe_inc() { pthread_mutex_lock(&lock); global_counter++; // ❌ 依赖共享数据 pthread_mutex_unlock(&lock); return global_counter; } ``` ### 常见误解澄清 - ❌ *"线程安全函数必然可重入"* **反例**:上述`thread_safe_inc()`函数加锁后线程安全,但若在`global_counter++`执行时被中断,中断处理程序再次调用此函数会导致**死锁**(锁已被持有)[^2]。 - ❌ *"可重入函数必然线程安全"* **成立**:因不依赖共享状态,多线程并发调用不会冲突 [^1][^3]。 > 关键结论:可重入性是比线程安全**更严格的要求**,它通过消除共享状态从根本上规避并发问题,而非依赖运行时同步[^2][^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值