`

线程安全和可重入性以及线程安全函数

阅读更多

线程安全:
       线程安全函数:在C语言中局部变量是在栈中分配的,任何未使用静态数据或其他共享资源的函数都是线程安全的。
                     使用全局变量的函数是非线程安全的。
                     使用静态数据或其他共享资源的函数,必须通过加锁的方式来使函数实现线程安全。

       线程安全的(Thread-Safe):
                   如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。
                   线程安全函数解决多个线程调用函数时访问共享资源的冲突问题。

       可重入(Reentrant):
                   函数可以由多于一个线程并发使用,而不必担心数据错误。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重  入性解决函数运行结果的确定性和可重复性。


可重入函数编写规范为:

1、不在函数内部使用静态或全局数据 
2、不返回静态或全局数据,所有数据都由函数的调用者提供。 
3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
4、如果必须访问全局变量,利用互斥机制来保护全局变量。
5、不调用不可重入函数。

两者之间的关系:
1、一个函数对于多个线程是可重入的,则这个函数是线程安全的。
2、一个函数是线程安全的,但并不一定是可重入的。【使用互斥锁实现的线程安全】
3、可重入性要强于线程安全性。

比如:
     strtok函数是既不可重入的,也不是线程安全的。加锁的strtok不是可重入的,但线程安全。
     而strtok_r既是可重入的,也是线程安全的。(具体可以查看man手册) 

_____________________________________________________________


为了写一个稳定的多线程程序,必须遵守线程安全,但不一定遵守可重入。 
    安全是指多个线程调用同一个函数,如果是线程安全的,那么每次的结果都是正确。

    可重入函数是指函数内部没有使用共享变量。
    可重入函数是线程安全函数的一个真子集。

    也就是说如果函数是可重入的,就可以保证它是线程安全的,当然有些不可重入的函数也是线程安全的,比如:【系统库函数实现的都是线程安全的。】
    系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改

其他任务调用这个函数的数据,从而导致不可预料的后果。那么什么是可重入函数呢?

    所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。不可重入函数在实时系统设计中被视为不安全函数。 int

global=0;
int foo1()
{
    return global++;
}
 

如果多个线程同时调用这个函数,每个线程返回的结果是什么?

很明显这个结果很难回答。因为你不知道他们这些线程是怎么工作的,谁先谁后都不知道,有的程序在线程的调度上只是简单的时间片轮转的策略,按找创建

线程的顺序应该可以得到正确答案,但是如果调度策略对修改了呢,比如是实时调度。

很明显一个可重入的函数的很必要的。

你或许想难道这个可重入函数不能使用全局变量了吗,你真正需要的或许只是线程安全,没有必要可重入,那么你可以这样改

int global=0;
int foo1()
{
    int local=global;
    return local++;
}
 

 

这就是为什么你或许看到很多程序都要在开始用一个局部变量保存一个全局变量的原因。

第二种情况 果函数中使用了静态变量或者返回静态变量或者静态数据结构,静态变量包括全局静态变量或者局部静态变量,函数也有是不可重入的,例如

int foo2()
{
    static int a=0;
    return a++;
}
 

要使它变得线程安全,其实很简单,在使用它之前,保存它的值。或者另一种情况这些变量都是只读的。

第三种情况 数中使用了malloc或者free函数,天呢,线程没有动态内存分配?不是的,malloc和free是不可重入的,但是是线程安全的,也就是开始所说的

概念,所以你大可以继续malloc和free。

第四种情况 函数体内调用了其他标准I/O函数

第五种情况 函数是singleton中的成员函数而且使用了不使用线程独立存储的成员变量 。

第六种情况 函数调用了不可冲入函数

下面一个函数就是线程安全但却不可重入的

static int *sharedID;
int* threadsafe_getID( char* name )
{
int *unsharedID;
P( &mutex );
sharedID = notThreadsafe_getID( name );
unsharedID = sharedID;
V( & mutex);
return unsharedID;
}

 

上面的函数通过P、V操作封装得到一个线程安全函数,却不是可重入函数。

 

转自:http://blog.csdn.net/xiaofei0859/article/details/5818511

 

线程安全函数 
• 概念: 
       线程安全的概念比较直观。一般说来,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。 
• 确保线程安全: 
       要确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄 存器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访 问时,如果要保证线程安全,则必须通过加锁的方式。 
• 线程不安全的后果: 
       线程不安全可能导致的后果是显而易见的——共享变量的值由于不同线程的访问,可能发生不可预料的变化,进而导致程序的错误,甚至崩溃。 

可重入函数 
• 概念: 
       可重入的概念基本没有比较正式的完整解释,多数的文档都只是说明什么样的情况才能保证函数可重入,但没有完整定义。按照Wiki上的说法,“A computer program or routine is described as reentrant if it can be safely executed concurrently; that is, the routine can be re-entered while it is already running.”根据笔者的经验,所谓“重入”,常见的情况是,程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理 函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处 理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的。 
• 确保可重入: 
       要确保函数可重入,需满足以下几个条件: 
       1、不在函数内部使用静态或全局数据 
       2、不返回静态或全局数据,所有数据都由函数的调用者提供。 
       3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。 
       4、不调用不可重入函数。 
• 不可重入的后果: 
       不可重入的后果主要体现在象信号处理函数这样需要重入的情况中。如果信号处理函数中使用了不可重入的函数,则可能导致程序的错误甚至崩溃。 

可重入与线程安全

       可重入与线程安全并不等同。一般说来,可重入的函数一定是线程安全的,但反过来不一定成立。它们的关系可用下图来表示:

       我们可以采用下面的变化过程来进一步说明上图: 
       - 如果一个函数中用到了全局或静态变量,那么它不是线程安全的,也不是可重入的; 
       - 如果我们对它加以改进,在访问全局或静态变量时使用互斥量或信号量等方式加锁,则可以使它变成线程安全的,但此时它仍然是不可重入的,因为通常加锁方式是针对不同线程的访问,而对同一线程可能出现问题; 
       - 如果将函数中的全局或静态变量去掉,改成函数参数等其他形式,则有可能使函数变成既线程安全,又可重入。 

       比如:strtok函数是既不可重入的,也不是线程安全的;加锁的strtok不是可重入的,但线程安全;而strtok_r既是可重入的,也是线程安全的。

分享到:
评论

相关推荐

    【Linux】— 线程安全 VS可重入

    线程安全 VS 可重入什么是线程安全和可重入常见的线程不安全的情况(重点)常见的线程安全的情况(重点)常见的可重入情况常见不可重入的情况可重入与线程安全联系可重入与线程安全区别(重点) 什么是线程安全和可重入 ...

    python使用threading.Condition交替打印两个字符

    所谓可重入锁,是只一个可以由一个线程递归获取的锁,此锁对象会维护当前锁的所有者(线程)和当前所有者递归获取锁的次数(本文在逻辑上和可重入锁没有任何关系,完全可以用一个普通锁替代)。 Python

    成熟量产扫地机代码 STM32 FreeRTos功能完整 代码注释清晰IIC、PWM、SPI、多路ADC与DMA、IAP

    知名大厂扫地机代码STM32FreeRTos功能完整 硬件驱动包含陀螺仪姿态传感器bmi160、电源管理bq24733等。...实时操作系统的设计目标是使应用程序在预定义的时间内完成操作,并保证操作的准确性和可预测性。

    深入解析Windows操作系统中文.part2.rar

    12.8 加密文件系统(EFS)安全性 775 第一次加密一个文件 778 解密过程 783 加密文件的备份 784 12.9 本章总结 785 第13章 网络 787 13.1 Windows的网络总体结构 787 OSI参考模型 787 Windows网络组件 789 13.2 网络...

    C语言和C++语言的区别是什么?快速入门C++编程的方法解析.docx

    C++还支持运算符重载、函数重载和模板等高级语言特性,可以提高代码的灵活性和可重用性。 除此之外,C++还支持异常处理、命名空间、RTTI(运行时类型识别)和多线程等特性,这些特性可以帮助程序员更好地编写高效、...

    thread.js::thread:使Web Worker和Worker线程像函数调用一样简单

    使用一个统一的API将CPU密集型任务卸载到node.js,Web浏览器和electronic中的工作线程。 在浏览器中使用Web worker,在节点12+中使用worker_threads ,在节点8至11中使用 。 产品特点 对异步功能和可观察对象的一流...

    window32 API大全 win32编程

    API函数是构筑整个Windows框架的基石,只有充分理解和利用API函数,才能深入到Windows的内部,充分发挥各种32位平台的强大功能和灵活性,才能成功地扩展和突破类库、控件和可视开发环境的限制。 Win32 API即为...

    可视化程序设计(内含源代码)

    本篇文章介绍了8个实验,涵盖了C#语言基础、面向对象编程、Window应用程序开发、C#高级特性...通过这些实验,读者将了解如何使用这些特性来提高代码的可读性、可维护性和可重用性。 第五,C#线程技术实验介绍了C#语言

    Java2核心技术.part5

    1.7 线程安全的集合 1.8 Callable和Future 1.9 执行器 1.10 同步器 1.11 线程和Swing工作器 第2章 集合 2.1 集合接口 2.2 具体的集合 2.3 集合框架 2.4 算法 2.5 遗留下来的集合 第3章 网络 3.1 连接到...

    Java2核心技术.part3

    1.7 线程安全的集合 1.8 Callable和Future 1.9 执行器 1.10 同步器 1.11 线程和Swing工作器 第2章 集合 2.1 集合接口 2.2 具体的集合 2.3 集合框架 2.4 算法 2.5 遗留下来的集合 第3章 网络 3.1 连接到...

    Java2核心技术.part1

    1.7 线程安全的集合 1.8 Callable和Future 1.9 执行器 1.10 同步器 1.11 线程和Swing工作器 第2章 集合 2.1 集合接口 2.2 具体的集合 2.3 集合框架 2.4 算法 2.5 遗留下来的集合 第3章 网络 3.1 连接到服务器 3.2 ...

    Java2核心技术.part6

    1.7 线程安全的集合 1.8 Callable和Future 1.9 执行器 1.10 同步器 1.11 线程和Swing工作器 第2章 集合 2.1 集合接口 2.2 具体的集合 2.3 集合框架 2.4 算法 2.5 遗留下来的集合 第3章 网络 3.1 连接到...

    Java2核心技术.part4

    1.7 线程安全的集合 1.8 Callable和Future 1.9 执行器 1.10 同步器 1.11 线程和Swing工作器 第2章 集合 2.1 集合接口 2.2 具体的集合 2.3 集合框架 2.4 算法 2.5 遗留下来的集合 第3章 网络 3.1 连接到...

    Java2核心技术.part2

    1.7 线程安全的集合 1.8 Callable和Future 1.9 执行器 1.10 同步器 1.11 线程和Swing工作器 第2章 集合 2.1 集合接口 2.2 具体的集合 2.3 集合框架 2.4 算法 2.5 遗留下来的集合 第3章 网络 3.1 连接到...

    python cookbook(第3版)

    6.12 读取嵌套和可变长二进制数据 6.13 数据的累加与统计操作 第七章:函数 7.1 可接受任意数量参数的函数 7.2 只接受关键字参数的函数 7.3 给函数参数增加元信息 7.4 返回多个值的函数 7.5 定义有默认参数...

    FCplusplus14:使用C ++ 14重制FC ++

    FCplusplus14 使用C ++ 14重新设想FC ++介绍在日益多线程的世界中,函数式编程的好处是很多的,更不用说源代码的可读性和可维护性的提高了。 以C ++ 11和C ++ 14的形式向C ++添加了几种范式转换功能,使得这种编程...

    CUDA下单源最短路径算法并行优化

    为设计基于固定序的 Bellman-Ford 算法在 CUDA 平台下并行优化方案,结合算法计算密集和数据密集的特点。...该并行优化方案验证了固定序在 CUDA 平台具有可行性和可移植性,可作为多平台研究参照。

    python面试题目-python-python经典面试题目-Python语言的基本概念-常用的功能和特性-编程范式-面试题目

    什么是Python中的迭代器(Iterator)和可迭代对象(Iterable)? Python中如何处理异常(Exception)?列举一些常见的异常类型。 什么是Python中的命名空间(Namespace)和作用域(Scope)? Python中的深拷贝和...

    C#实训教程

    6.3 类型的安全性 107 6.4 类型转换 108 6.5 装箱和拆箱 111 6.6 对象的相等比较 112 6.7 引用类型的相等比较 112 6.8 运算符重载 114 6.9 运算符的工作方式 114 6.10 用户定义的数据类型转换 118 6.11 执行用户定义...

Global site tag (gtag.js) - Google Analytics