来自: Jessica要努力了。。
链接:https://www.cnblogs.com/wuchanming/p/4020184.html
下文是在看csapp的时侯引起的一些思索,虽然之前看anup的时侯也有所了解,不过时间有点长了判断线程是否在运行 linux,所以有点忘掉了,当再度在csapp见到这部份内容的时侯有了更多的理解。
可重入函数
当一个被捕获的讯号被一个进程处理时,进程执行的普通的指令序列会被一个讯号处理器暂时地中断。它首先执行该讯号处理程序中的指令。倘若从讯号处理程序返回(比如没有调用exit或longjmp),则继续执行在捕获到讯号时进程正在执行的正常指令序列(这和当一个硬件中断发生时所发生的事情相像)。并且在讯号处理器里,我们并不晓得当讯号被捕获时进程正在执行那里的代码。
假如进程正使用malloc在它的堆上分配额外的显存,而此时因为捕捉到讯号而插入执行该讯号处理程序,其中又调用了malloc,这会发生哪些呢?或则,假如进程正调用一个把结果储存在一个静态区域里的函数到一半,例如getpwnam,而我们在讯号处理器里调用相同的函数,又会发生哪些呢?在malloc的事例里,进程可能会受到严重破坏,由于malloc一般维护它所有分配过的区域的数组,而插入执行讯号处理程序时,进程可能正在修改此链接表。
在getpwnam的事例里,返回给普通调用者的信息可能被返回给讯号处理器的信息覆盖。
SUS规定了必须保证是可以再入的函数。下表列举了这种再入函数:
一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转到OS调度下去执行另外一段代码,而返回控制时不会出现哪些错误。可重入(reentrant)函数可以由少于一个任务并发使用,而毋须害怕数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或则使用讯号量,或则在代码的关键部份禁用中断)。
可重入函数可以在任意时刻被中断,稍后再继续运行,不会遗失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。
讯号安全,当然也就是异步讯号安全,是说线程在讯号处理函数当中,不管以任何方式调用你的这个函数若果不死锁不更改数据,那就是讯号安全的。为此,我觉得可重入与异步讯号安全是一个概念。
线程安全
线程安全:一个函数被称为线程安全的,当且仅当被多个并发线程反复的调用时,它会仍然形成正确的结果。
有一类重要的线程安全函数,称作可重入函数,其特征在于它们具有一种属性:当它们被多个线程调用时,不会引用任何共享的数据。
虽然线程安全和可重入有时会(不正确的)被用做同义词,而且它们之间还是有清晰的技术差异的。可重入函数是线程安全函数的一个真子集。
可重入与线程安全的区别及联系
可重入函数:重入即表示重复步入,首先它意味着这个函数可以被中断,其次意味着它不仅使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入判断线程是否在运行 linux,可以容许有该函数的多个副本在运行,因为它们使用的是分离的栈,所以不会相互干扰。
可重入函数是线程安全函数,并且反过来,线程安全函数未必是可重入函数。
实际上,可重入函数极少,APUE10.6节中描述了SingleUNIXSpecification说明的可重入的函数,只有115个;APUE12.5节中描述了POSIX.1中不能保证线程安全的函数,只有89个。
讯号如同硬件中断一样,会打断正在执行的指令序列。讯号处理函数难以判定捕获到讯号的时侯中标麒麟linux,进程在何处运行。假如讯号处理函数中的操作与打断的函数的操作相同,但是这个操作中有静态数据结构等,当讯号处理函数返回的时侯(其实这儿讨论的是讯号处理函数可以返回),恢复原先的执行序列,可能会造成讯号处理函数中的操作覆盖了之前正常操作中的数据。
不可重入的几种情况
使用静态数据结构,例如getpwnam,getpwuid:假如讯号发生时正在执行getpwnam,讯号处理程序中执行getpwnam可能覆盖原先getpwnam获取的旧值
虽然对于可重入函数,在讯号处理函数中使用也须要注意一个问题就是errno。一个线程中只有一个errno变量,讯号处理函数中使用的可重入函数也有可能会更改errno。诸如,read函数是可重入的,并且它也有可能会更改errno。为此,正确的做法是在讯号处理函数开始,先保存errno;在讯号处理函数退出的时侯,再恢复errno。
比如,程序正在调用printf输出,而且在调用printf时,出现了讯号,对应的讯号处理函数也有printf句子,都会造成两个printf的输出混杂在一起。
若果是给printf加锁的话,同样是里面的情况都会造成死锁。对于这些情况,采用的方式通常是在特定的区域屏蔽一定的讯号。
屏蔽讯号的方式:
signal(SIGPIPE, SIG_IGN); // 忽略一些信号
sigprocmask();// sigprocmask 只为单线程定义的
pthread_sigmask(); // pthread_sigmasks 可以在多线程中使用
如今看来信号异步安全和可重入的限制其实是一样的,所以这儿把它们等同看待;
线程安全:假如一个函数在同一时刻可以被多个线程安全的调用,就称该函数是线程安全的。Malloc函数是线程安全的。
不须要共享时,请为每位线程提供一个专用的数据副本。假如共享十分重要,则提供显式同步,以确保程序以确定的形式操作。通过将过程包含在句子中来锁定和解除锁定互斥linux入门,可以使不安全过程弄成线程安全过程,并且可以进行串行化。
好多函数并不是线程安全的,由于她们返回的数据是储存在静态的显存缓冲区中的。通过更改插口,由调用者自行提供缓冲区就可以使这种函数变为线程安全的。
操作系统实现支持线程安全函数的时侯,会对POSIX.1中的一些非线程安全的函数提供一些可替换的线程安全版本。
比如,gethostbyname()是线程不安全的,在Linux中提供了gethostbyname_r()的线程安全实现。
函数名子前面加上_r,以表明这个版本是可重入的(对于线程可重入,也就是说是线程安全的,但并不是说对于讯号处理函数也是可重入的,或则是异步讯号安全的)。
多线程程序中常见的疏漏性问题:
总结
可重入与线程安全是两个独立的概念,都与函数处理资源的方法有关。
首先,可重入和线程安全是两个并不等同的概念,一个函数可以是可重入的,也可以是线程安全的,可以二者均满足,可以三者皆不满足(该描述严格的说存在漏洞,参见第二条)。
其次,从集合和逻辑的角度看,可重入是线程安全的子集,可重入是线程安全的充分非必要条件。可重入的函数一定是线程安全的,然过来则不创立。
第三,POSIX中对可重入和线程安全这两个概念的定义:
以上两者的关系为:可重入函数必然是线程安全函数和异步讯号安全函数;线程安全函数不一定是可重入函数。
可重入与线程安全的区别彰显在能够在signal处理函数中被调用的问题上,可重入函数在signal处理函数中可以被安全调用,因而同时也是Async-Signal-SafeFunction;而线程安全函数不保证可以在signal处理函数中被安全调用,假如通过设置讯号阻塞集合等方式保证一个非可重入函数不被讯号中断,这么它也是Async-Signal-SafeFunction。
值得一提的是POSIX1003.1的SystemInterface缺省是Thread-Safe的,但不是Async-Signal-Safe的。Async-Signal-Safe的须要明晰表示,例如fork()和signal()。
一个非可重入函数一般(虽然不是所有情况下)由它的外部插口和使用方式即可进行判别。诸如:strtok()是非可重入的,由于它在内部储存了被标记分割的字符串;ctime()函数也是非可重入的,它返回一个指向静态数据的表针,而该静态数据在每次调用中都被覆盖重画。
一个线程安全的函数通过加锁的形式来实现多线程对共享数据的安全访问。线程安全这个概念,只与函数的内部实现有关,而不影响函数的外部插口。在C语言中,局部变量是在栈上分配的。因而,任何未使用静态数据或其他共享资源的函数都是线程安全的。
目前的AIX版本中,以下函数库是线程安全的:
使用全局变量(的函数)是非线程安全的。这样的信息应当以线程为单位进行储存,这样对数据的访问就可以串行化。一个线程可能会读取由另外一个线程生成的错误代码。在AIX中,每位线程有独立的errno变量。
最后让我们来设想一个线程安全但不可重入的函数:
假定函数func()在执行过程中须要访问某个共享资源,因而为了实现线程安全,在使用该资源前加锁,在不须要资源解锁。
假定该函数在某次执行过程中,在早已获得资源锁以后,有异步讯号发生,程序的执行流转交给对应的讯号处理函数;再假定在该讯号处理函数中也须要调用函数func(),这么func()在此次执行中仍会在访问共享资源前企图获得资源锁,但是我们晓得前一个func()实例已经获得该锁,因而讯号处理函数阻塞——另一方面,讯号处理函数结束前被讯号中断的线程是难以恢复执行的,其实也没有释放资源的机会,这样就出现了线程和讯号处理函数之间的死锁局面。
因而,func()虽然通过加锁的形式能保证线程安全,而且因为函数体对共享资源的访问,因而是非可重入。
良许个人陌陌
添加良许个人陌陌即送3套程序员必看资料
→精选技术资料共享
→高手如云交流社群
本公众号全部博文已整理成一个目录,请在公众号里回复「m」获取!