线程安全与重入
线程安全与重入
1、定义
网上现有的资料,对于函数线程安全与可重入的概念,各种解释都有,但感觉都有偏差。我感觉函数线程安全与函数的可重入是两个有点相似但又不相关的概念,根据实际使用经验,我给出两者定义(可能定义不对,请大佬斧正):
可重入函数: 函数未执行完时再次调用自己,比如递归,模态打开窗口,QDbus使用BlockWithGui方式调用。这是个单线场景中的概念,一般使用flag来保护重入逻辑,所以不是多线程安全的(当然如果用原子量来保护那同时也是线程安全,注意不能使用mutex来保护,原因见下面线程安全定义)。
线程安全函数: 线程安全大家很好理解,一般使用加锁的方式来达到线程安全的目的。但是线程安全的函数多数是不是可重入的,跟网上的结论相反。不可重入的原因是锁本身不可重入,比如Qt与pthread的mutex重入就会死锁,stl的mutex重入会崩溃。但是线程安全函数,是可以同时具备可重入能力的,关键就在于使用可重入锁,可以使用双重判断来自己封装一个可重入锁,另外windows系统自带的mutex是可重入的,pthread的mutex可以使用PTHREAD_MUTEX_RECURSIVE标记使之可重入。
2、演示重入导致死锁
2.1 最简单的重入死锁演示
int main(int argc, char *argv[])
{
QBasicMutex mutex;
mutex.lock();
mutex.lock();
return 0;
}
最后死锁栈为:
(gdb) bt
#0 syscall () at ../sysdeps/unix/sysv/linux/aarch64/syscall.S:38
#1 0x0000007f81bf8e2c in __cxa_guard_acquire () from /lib/aarch64-linux-gnu/libstdc++.so.6
</pre>
2.2 结合信号演示重入死锁
上面演示太简单大家当然不会这么去干,这里结合信号演示稍微复杂点的,复杂点意味着重入比较隐蔽,写代码时容易踩坑。后面再写个wiki解读QGlobalStatic,并在里面演示一个实际碰到的更隐蔽的重入死锁。
<pre>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
//gcc -lpthread signaltest.c
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
time_t g_time = 0;
int elapsed_seconds()
{
time_t now = time(NULL);
return now - g_time;
}
void* threadrun(void *vp)
{
pthread_mutex_lock(&g_mutex);
printf("thread run 1 -- before sleep, %x, %d\n", pthread_self(), elapsed_seconds());
sleep(5);
printf("thread run 2 -- middle sleep, %x, %d\n", pthread_self(), elapsed_seconds());
sleep(5);
printf("thread run 3 -- after sleep, %x, %d\n", pthread_self(), elapsed_seconds());
pthread_mutex_unlock(&g_mutex);
printf("thread run 4 -- after unlock, %x, %d\n", pthread_self(), elapsed_seconds());
return 0;
}
void sig_handler(int signo)
{
pthread_mutex_lock(&g_mutex);//分别测试加锁与不锁
printf("sig_handler 1 -- before sleep, %x, %d\n", pthread_self(), elapsed_seconds());
sleep(5);
printf("sig_handler 2 -- middle sleep, %x, %d\n", pthread_self(), elapsed_seconds());
sleep(5);
printf("sig_handler 3 -- after sleep, %x, %d\n", pthread_self(), elapsed_seconds());
pthread_mutex_unlock(&g_mutex);//分别测试加锁与不锁
printf("sig_handler 4 -- after unlock, %x, %d\n", pthread_self(), elapsed_seconds());
}
int main(int argc, char* argv[])
{
g_time = time(NULL);
signal(SIGUSR1, sig_handler);
pthread_t p;
pthread_create(&p, NULL, &threadrun, NULL);
sleep(2);
pthread_kill(p, SIGUSR1);
pthread_join(p, NULL);//not care thread return
pthread_mutex_destroy(&g_mutex);
return 0;
}
//死锁线程栈如下:
// #0 __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:103
// #1 0x00007fc48a147714 in __GI___pthread_mutex_lock (mutex=0x4040a0 <g_mutex>) at ../nptl/pthread_mutex_lock.c:80
// #2 0x00000000004012d6 in sig_handler ()
// #3 <signal handler called>
// #4 0x00007fc48a042720 in __GI___nanosleep (requested_time=requested_time@entry=0x7fc489f77e90, remaining=remaining@entry=0x7fc489f77e90)
// at ../sysdeps/unix/sysv/linux/nanosleep.c:28
// #5 0x00007fc48a04262a in __sleep (seconds=0) at ../sysdeps/posix/sleep.c:55
// #6 0x0000000000401231 in threadrun ()
// #7 0x00007fc48a144fa3 in start_thread (arg=<optimized out>) at pthread_create.c:486
// #8 0x00007fc48a0754cf in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95