abort()函数不是多线程安全的,但它是异步信号安全的。
2010-07-26 21:56
218 查看
今天遇到了工作以来最深入的问题,就是关于
abort
函数的多线程安全性问题,应该写一篇文章,纪念一下,希望自己以后能够学到更多关于
Linux
内核的知识,祝愿自己以后工作越来越顺利。
首先,需要说明一下,什么是多线程安全以及异步信号安全。
所谓多线程安全,我们称为
MT-Safe
,就是指同一个函数,同时被多个线程并行调用时,不会出现问题,也就是说,其执行结果就和该函数被串行执行多次的结果一样。打个比方,如果一个函数在不加锁的情况下使用了全局变量或静态变量,那么,当它被多个线程同时调用时,有可能出现结果不一致的情况,这样的函数就不是多线程安全的,我们称为
unsafe
。
所谓异步信号安全,就是指一个函数可以被一个信号处理函数不加限制地调用,而不会出现问题。这个概念很模糊,一开始我也没有弄懂。要说清楚这个问题,就必须看看什么样的函数不能被信号处理函数安全地调用。
这里谈论的信号,主要是指异步信号。所谓异步信号,就是随时都有可能出现的信号。从宏观上说,如果一个进程正执行一个函数
func()
,执行到一半还没有退出的时候,收到一个异步信号,此时,处理器必须转而执行相应的信号处理函数
handle()
,如果正巧,
handle
也调用了
func()
,那就有可能出现问题。例如,
func
使用了全局变量,如果不加锁,那就和上面提到的多线程安全性问题一样,结果会不一致;如果加锁,试想,在进入
handle
之前,
func
已经在该全局变量获得了锁,还没有释放,就进入了
handle
,
handle
再次调用
func
,又一次申请同一个锁,这就形成了死锁。所以,这种情况下,无论加不加锁,也就是无论是否多线程安全,这个函数
func
都不是异步信号安全的。
有了上面的铺垫,我们就可以来看一下
abort()
函数的源码了。由于源码太长,我们只列出对本次讨论有帮助的一部分,完整的源码可以在
glibc
源码中
stdlib
文件夹下的
abort.c
文件中找到。
void
abort (void)
{
struct sigaction act;
sigset_t sigs;
/* First acquire the lock.
*/
__libc_lock_lock_recursive (lock);
//
注意,此处是递归锁(
8
)
......
if (stage == 1)
{
++stage;
fflush (NULL);
//
此处的
fflush
函数引起了
abort
的多线程安全性问题(
13
)
}
......
}
从代码的第
8
行可以看出,
lock
没有在
abort
中定义,它是一个全局变量,
abort
使用了这个全局变量,但是加锁了,而在余下的代码中,并没有发现
abort
使用其他全局变量或静态变量,所以,
abort
是
MT-Safe
的。由如同前面所说,由于
abort
加了锁,所以在信号处理函数中调用
abort
,会导致单线程死锁,因此它不是异步信号安全的。
于是,我们得出,
abort
是多线程安全的,但不是异步信号安全的。
可惜,结果正好相反,
abort
不是多线程安全的,而且它是异步信号安全的。我们完全错了。
首先,我们来看一下,为什么
abort
是异步信号安全的。我们认为它不是异步信号安全的,主要是因为第
8
行那个锁,在信号处理函数中调用
abort
会引起死锁。但是,注意到这个锁的名字,
__libc_lock_lock_recursive()
,它是一个递归锁。
所谓递归锁,是
glibc
实现的一种锁机制,这种锁支持函数的递归调用,用来防止单线程死锁。也就是说,使用递归锁,当线程
A
获得了一个锁,线程
B
要想再获得这个锁,是不可能的,但是如果在线程
A
中,又一次申请该锁,那么是可以获得的。递归锁在线程之间的表现,和普通的锁没有区别,但是在同一个线程中,却可以获得多次,当获得了几次就释放了几次以后,该锁才会被线程释放,其他的线程才能够获得该锁。
这样一来,事情就明了了,信号处理函数运行的上下文任然是当前进程,所以当
abort
已经获得了全局变量
lock
上的锁,即使某个信号处理函数再一次调用
abort
申请该锁,它也能得到,而不会引起单线程死锁,因此,
abort
是异步信号安全的。
同时,
abort
又不是多线程安全的,这是为什么呢?难道加了锁,还会引起多线程安全性的问题吗?不,不会,这里的多线程安全性与这个所无关,与死锁也无关,这里的问题是由第
13
行的函数
fflush()
引起的。
参看源码可知,此处的
fflush
并不是
glibc
中实现的
fflush
函数,而是一个宏,
# define fflush(s) _IO_flush_all_lockp (0)
这个宏调用了
_IO_flush_all_lockp()
函数,问题就处在
_IO_flush_all_lockp
里面。
int
_IO_flush_all_lockp (int do_lock)
{
......
while (fp != NULL)
{
run_fp = fp;
//
此处
run_fp
是一个全局变量,
fp
是局部变量
if (do_lock)
_IO_flockfile (fp);
......
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
......
}
请先仔细看一下上面的代码逻辑,
do_lock
是
_IO_flush_all_lockp
函数的参数,
fp
是一个局部指针,只想一个文件描述符,只有在
do_lock
不为空时,
_IO_flush_all_lockp
函数才对
fp
加锁。
我们知道,虽然
fp
是一个局部指针,但是它所指向的文件描述符可能被多个线程使用,当
_IO_flush_all_lockp
函数判断了
fp != NULL
以后,进入循环,这是,如果没有对
fp
加锁,很有可能会有另一个线程调用了
fclose()
函数将
fp
指向的这个文件描述符关闭,如此一来,当再一次回到
_IO_flush_all_lockp
的逻辑时,
fp
实际上已经为空了,再对它进行操作,是很危险的。
我们再看一下
abort
的第
13
行代码,发现
abort
调用
fflush(s)
宏,它传给
_IO_flush_all_lockp
的参数
do_lock
就是
NULL
,因此,实际上,
_IO_flush_all_lockp
在使用
fp
时,并没有加锁。因此,严格来讲,
abort
和
fclose
同时调用,是由潜在危险的。
abort
应该不是多线程安全的。
综上所述,原来对
abort
的判断是完全错误的,
abort
不是多线程安全的,但是它是异步信号安全的。
疑问:我对递归锁的了解还不是很深入,难道递归锁不会引起单线程内对全局变量或静态变量的访问问题吗?希望有经验的朋友能够帮助我,谢谢。
abort
函数的多线程安全性问题,应该写一篇文章,纪念一下,希望自己以后能够学到更多关于
Linux
内核的知识,祝愿自己以后工作越来越顺利。
首先,需要说明一下,什么是多线程安全以及异步信号安全。
所谓多线程安全,我们称为
MT-Safe
,就是指同一个函数,同时被多个线程并行调用时,不会出现问题,也就是说,其执行结果就和该函数被串行执行多次的结果一样。打个比方,如果一个函数在不加锁的情况下使用了全局变量或静态变量,那么,当它被多个线程同时调用时,有可能出现结果不一致的情况,这样的函数就不是多线程安全的,我们称为
unsafe
。
所谓异步信号安全,就是指一个函数可以被一个信号处理函数不加限制地调用,而不会出现问题。这个概念很模糊,一开始我也没有弄懂。要说清楚这个问题,就必须看看什么样的函数不能被信号处理函数安全地调用。
这里谈论的信号,主要是指异步信号。所谓异步信号,就是随时都有可能出现的信号。从宏观上说,如果一个进程正执行一个函数
func()
,执行到一半还没有退出的时候,收到一个异步信号,此时,处理器必须转而执行相应的信号处理函数
handle()
,如果正巧,
handle
也调用了
func()
,那就有可能出现问题。例如,
func
使用了全局变量,如果不加锁,那就和上面提到的多线程安全性问题一样,结果会不一致;如果加锁,试想,在进入
handle
之前,
func
已经在该全局变量获得了锁,还没有释放,就进入了
handle
,
handle
再次调用
func
,又一次申请同一个锁,这就形成了死锁。所以,这种情况下,无论加不加锁,也就是无论是否多线程安全,这个函数
func
都不是异步信号安全的。
有了上面的铺垫,我们就可以来看一下
abort()
函数的源码了。由于源码太长,我们只列出对本次讨论有帮助的一部分,完整的源码可以在
glibc
源码中
stdlib
文件夹下的
abort.c
文件中找到。
void
abort (void)
{
struct sigaction act;
sigset_t sigs;
/* First acquire the lock.
*/
__libc_lock_lock_recursive (lock);
//
注意,此处是递归锁(
8
)
......
if (stage == 1)
{
++stage;
fflush (NULL);
//
此处的
fflush
函数引起了
abort
的多线程安全性问题(
13
)
}
......
}
从代码的第
8
行可以看出,
lock
没有在
abort
中定义,它是一个全局变量,
abort
使用了这个全局变量,但是加锁了,而在余下的代码中,并没有发现
abort
使用其他全局变量或静态变量,所以,
abort
是
MT-Safe
的。由如同前面所说,由于
abort
加了锁,所以在信号处理函数中调用
abort
,会导致单线程死锁,因此它不是异步信号安全的。
于是,我们得出,
abort
是多线程安全的,但不是异步信号安全的。
可惜,结果正好相反,
abort
不是多线程安全的,而且它是异步信号安全的。我们完全错了。
首先,我们来看一下,为什么
abort
是异步信号安全的。我们认为它不是异步信号安全的,主要是因为第
8
行那个锁,在信号处理函数中调用
abort
会引起死锁。但是,注意到这个锁的名字,
__libc_lock_lock_recursive()
,它是一个递归锁。
所谓递归锁,是
glibc
实现的一种锁机制,这种锁支持函数的递归调用,用来防止单线程死锁。也就是说,使用递归锁,当线程
A
获得了一个锁,线程
B
要想再获得这个锁,是不可能的,但是如果在线程
A
中,又一次申请该锁,那么是可以获得的。递归锁在线程之间的表现,和普通的锁没有区别,但是在同一个线程中,却可以获得多次,当获得了几次就释放了几次以后,该锁才会被线程释放,其他的线程才能够获得该锁。
这样一来,事情就明了了,信号处理函数运行的上下文任然是当前进程,所以当
abort
已经获得了全局变量
lock
上的锁,即使某个信号处理函数再一次调用
abort
申请该锁,它也能得到,而不会引起单线程死锁,因此,
abort
是异步信号安全的。
同时,
abort
又不是多线程安全的,这是为什么呢?难道加了锁,还会引起多线程安全性的问题吗?不,不会,这里的多线程安全性与这个所无关,与死锁也无关,这里的问题是由第
13
行的函数
fflush()
引起的。
参看源码可知,此处的
fflush
并不是
glibc
中实现的
fflush
函数,而是一个宏,
# define fflush(s) _IO_flush_all_lockp (0)
这个宏调用了
_IO_flush_all_lockp()
函数,问题就处在
_IO_flush_all_lockp
里面。
int
_IO_flush_all_lockp (int do_lock)
{
......
while (fp != NULL)
{
run_fp = fp;
//
此处
run_fp
是一个全局变量,
fp
是局部变量
if (do_lock)
_IO_flockfile (fp);
......
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
......
}
请先仔细看一下上面的代码逻辑,
do_lock
是
_IO_flush_all_lockp
函数的参数,
fp
是一个局部指针,只想一个文件描述符,只有在
do_lock
不为空时,
_IO_flush_all_lockp
函数才对
fp
加锁。
我们知道,虽然
fp
是一个局部指针,但是它所指向的文件描述符可能被多个线程使用,当
_IO_flush_all_lockp
函数判断了
fp != NULL
以后,进入循环,这是,如果没有对
fp
加锁,很有可能会有另一个线程调用了
fclose()
函数将
fp
指向的这个文件描述符关闭,如此一来,当再一次回到
_IO_flush_all_lockp
的逻辑时,
fp
实际上已经为空了,再对它进行操作,是很危险的。
我们再看一下
abort
的第
13
行代码,发现
abort
调用
fflush(s)
宏,它传给
_IO_flush_all_lockp
的参数
do_lock
就是
NULL
,因此,实际上,
_IO_flush_all_lockp
在使用
fp
时,并没有加锁。因此,严格来讲,
abort
和
fclose
同时调用,是由潜在危险的。
abort
应该不是多线程安全的。
综上所述,原来对
abort
的判断是完全错误的,
abort
不是多线程安全的,但是它是异步信号安全的。
疑问:我对递归锁的了解还不是很深入,难道递归锁不会引起单线程内对全局变量或静态变量的访问问题吗?希望有经验的朋友能够帮助我,谢谢。
相关文章推荐
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- 函数的可重入性、线程安全函数、异步信号安全函数
- Linux 多线程应用中编写安全的信号处理函数
- 异步信号安全函数列表
- 多线程之可重入版本异步信号安全的putenv_r(十)
- 多线程应用中编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数
- Linux 多线程应用中如何编写安全的信号处理函数(转载IBM)
- Linux 多线程应用中如何编写安全的信号处理函数
- 标准的异步信号安全函数
- Linux 多线程应用中如何编写安全的信号处理函数【转】
- Linux 多线程应用中如何编写安全的信号处理函数