最近测试我们自己改进的redis,发现在做rdb时,子进程会一直hang住,gdb attach上,堆栈如下:

(gdb) bt
#0  0x0000003f6d4f805e in __lll_lock_wait_private () from /lib64/libc.so.6
#1  0x0000003f6d49dcad in _L_lock_2164 () from /lib64/libc.so.6
#2  0x0000003f6d49da67 in __tz_convert () from /lib64/libc.so.6
#3  0x0000000000421004 in redisLogRaw (level=2, msg=0x7fff9f412b50 "[INFQ_INFO]: [infq.c:1483] infq persistent dump, suffix: 405665, start_index: 35637626, ele_count: 4") at redis.c:332
#4  0x0000000000421256 in redisLog (level=2, fmt=0x4eedcf "[INFQ_INFO]: %s") at redis.c:363
#5  0x000000000043926b in infq_info_log (msg=0x7fff9f413090 "[infq.c:1483] infq persistent dump, suffix: 405665, start_index: 35637626, ele_count: 4") at object.c:816
#6  0x00000000004b5677 in infq_log (level=1, file=0x501465 "infq.c", lineno=1483, fmt=0x5024e0 "infq persistent dump, suffix: %d, start_index: %lld, ele_count: %d") at logging.c:81
#7  0x00000000004b37f8 in dump_push_queue (infq=0x17d07f0) at infq.c:1480
#8  0x00000000004b1566 in infq_dump (infq=0x17d07f0, buf=0x7fff9f413650 "", buf_size=1024, data_size=0x7fff9f413a5c) at infq.c:720
#9  0x00000000004440e6 in rdbSaveObject (rdb=0x7fff9f413c50, o=0x7f8c0b4d0470) at rdb.c:600
#10 0x000000000044429c in rdbSaveKeyValuePair (rdb=0x7fff9f413c50, key=0x7fff9f413b90, val=0x7f8c0b4d0470, expiretime=-1, now=1434687031023) at rdb.c:642
#11 0x0000000000444471 in rdbSaveRio (rdb=0x7fff9f413c50, error=0x7fff9f413c4c) at rdb.c:686
#12 0x0000000000444704 in rdbSave (filename=0x7f8c0b410040 "dump.rdb") at rdb.c:750
#13 0x00000000004449cd in rdbSaveBackground (filename=0x7f8c0b410040 "dump.rdb") at rdb.c:831
#14 0x0000000000422b0e in serverCron (eventLoop=0x7f8c0b45a150, id=0, clientData=0x0) at redis.c:1240
#15 0x000000000041d47e in processTimeEvents (eventLoop=0x7f8c0b45a150) at ae.c:311
#16 0x000000000041d7c0 in aeProcessEvents (eventLoop=0x7f8c0b45a150, flags=3) at ae.c:423
#17 0x000000000041d8de in aeMain (eventLoop=0x7f8c0b45a150) at ae.c:455
#18 0x0000000000429ae3 in main (argc=2, argv=0x7fff9f414168) at redis.c:3843

都阻塞在redisLog上,用于打印日志。在打印日志时,需要调用localtime生成时间。查看glibc代码glibc-2.9/time/localtime.c:

/* Return the `struct tm' representation of *T in local time,using *TP to store the result.  */
struct tm *
__localtime_r (t, tp)const time_t *t;struct tm *tp;
{return __tz_convert (t, 1, tp);
}
weak_alias (__localtime_r, localtime_r)/* Return the `struct tm' representation of *T in local time.  */
struct tm *
localtime (t)const time_t *t;
{return __tz_convert (t, 1, &_tmbuf);
}
libc_hidden_def (localtime)

无论localtime还是localtime_r都是调用__tz_convert函数完成实际功能的,接着看这个函数,在glibc-2.9/time/tzset.c中:

/* This locks all the state variables in tzfile.c and this file.  */
__libc_lock_define_initialized (static, tzset_lock)/* Return the `struct tm' representation of *TIMER in the local timezone.Use local time if USE_LOCALTIME is nonzero, UTC otherwise.  */
struct tm *
__tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
{long int leap_correction;int leap_extra_secs;if (timer == NULL){__set_errno (EINVAL);return NULL;}// 加锁__libc_lock_lock (tzset_lock);// 一些出来逻辑// 解锁__libc_lock_unlock (tzset_lock);return tp;
}

这个函数是用的tzset_lock全局锁,是一个static变量。由于加锁访问,所以这个localtime_r是线程安全的,但是localtime使用全局变量所以不是线程安全的。但这两个函数都不是信号安全的,如果在信号处理函数中使用,就要考虑到死锁的情况。比如,程序调用localtime_r,加锁后信号发生,信号处理函数中也调用localtime_r的话,会因为获取不到锁所以一直阻塞。

上述localtime死锁,为什么在原生redis中不会发生?

因为,原生redis中不会多线程调用localtime函数,在fork子进程时,对于localtime的调用都是完整的,即锁以及释放了。

由于我们改进的redis中,使用了多线程,并且会调用redisLog打印日志,所以在fork子进程时,某个线程可能正处于localtime函数调用中(加锁了,但尚未解锁),这种情况下,子进程以copy-on-write方式共享主进程的内存空间,所以对应localtime的锁也是被占用的情况,所以子进程一直阻塞。

那么,解决方案呢?

如果,对于锁我们有控制权,那么在调用fork创建子进程前,可以通过库函数pthead_atfork加解锁,达到一致状态。

     #include <pthread.h>intpthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

prepare函数指针在fork前被调用,parent和child分别在父子进程中fork返回后调用。这样,可以在prepare中释放所有的锁,parent中按需要进行加锁。

由于没有办法操作localtime使用的锁,所以上述方式行不通。这里,我们采用了折中的方法:依靠redis中serverCron定时器去更新localtime并保存到全局变量中,组件的多线程打印日志时,只是获取缓存的全局变量,避免了多线程调用localtime函数。由于serverCron以最多10ms的间隔执行,所以不会出现太多误差,对于日志来说完全可用。
最后总结一下,这种有全局锁的函数都不是信号安全的,比如localtime,free,malloc等。同时这类函数,在多线程模式下调用,在fork子进程时可能会死锁。避免出现这种情况的方式,就是保证在fork时不会出现加锁的情况(可以通过避免多线程调用,或者通过自定义的锁区控制)。

localtime死锁——多线程下fork子进程相关推荐

  1. android fork 子进程,fork子进程

    title: fork子进程 data: 2019/3/21 20:24:39 toc: true 这里实在学习socket编程前的小知识点,用来创建多个服务端 学习文档 速记 fork并不保证父子进 ...

  2. mysql master线程 fork_多线程中fork的坑

    多线程中fork的坑 问题所在 在写oj的时候,由于使用了线程池,并且在获取用户程序运行结果的时候使用的是管道进行子进程的标准输出的获取, 最后带来了一个问题,就是发现本来线程池有5个任务,最后调试信 ...

  3. 从一道面试题谈linux下fork的运行机制

    http://kb.cnblogs.com/page/76622/ 今天一位朋友去一个不错的外企面试linux开发职位,面试官出了一个如下的题目: 给出如下C程序,在linux下使用gcc编译: #i ...

  4. 机制 linux_从一道面试题谈linux下fork的运行机制

    今天一位朋友去一个不错的外企面试linux开发职位,面试官出了一个如下的题目: 给出如下C程序,在linux下使用gcc编译: #include "stdio.h" #includ ...

  5. LINUX下FORK的运行机制详细解析

    摘要:由于fork函数运行机制的复杂性,造就了当两个fork并排时,问题就变得很复杂.解这个题的关键,一是要对linux下进程的机制有一定认识,二是抓住上文提到的几个关于fork的关键点. 今天一位朋 ...

  6. linux资源异常无法fork,linux 下 fork 后的文件资源处理问题

    我们都知道 linux 下 fork 一个子进程出来,他能够继承父进程的文件资源,网络资源等,也从父进程那里拷贝了代码段,数据段,缓冲区等等到自己这里有了新的一份,那么,如果父子进程对于打开的文件资源 ...

  7. 《Linux多线程muduo》读书笔记1——多线程下的析构函数

    对象的安全析构 文章目录 对象的安全析构 1. 问题 2. 作为数据成员的mutex不能保护析构函数 3. 小心swap 4. 垃圾回收 5. 管理共享资源的一个万能方法 5.1 shared_ptr ...

  8. 进击谷歌:多线程下程序执行顺序怎么稳定不乱?

    面试官您好,我是来面试的 您好,我是这次的面试官,先介绍一下自己把 我是女孩,blala .... 那问一个多线程的问题吧,在一个多线程的环境中,怎么能保证一系列方法的执行顺序呢? 01 PART 题 ...

  9. 多线程下ArrayList类线程不安全的解决方法及原理

    多线程下ArrayList类线程不安全的解决方法及原理 参考文章: (1)多线程下ArrayList类线程不安全的解决方法及原理 (2)https://www.cnblogs.com/fangting ...

最新文章

  1. Android 金钱计算BigDecimal 的使用
  2. java中sum=a+aa+aaa_Java面向对象基础IO系统
  3. ubuntu设置鼠标单击打开文件夹或者文件
  4. Delphi中类型转换函数
  5. 会返回两次_嫦娥五号为何用独特的半弹道式返回方式?原来有更深远的考虑……...
  6. 《操作系统》OS学习(三):系统调用
  7. 智能化连锁门店解决方案
  8. TensorFlow 1.9.0正式版来了!新手指南全新改版,支持梯度提升树估计器
  9. c md5加密 和java不一样_C#的MD5加密为什么和JAVA的加密出来的结果不一样?
  10. 佳能600D入门秘籍(三)
  11. K歌、短视频技术最佳实践——“唱吧”音视频技术探索
  12. Android Studio设置自动换行快捷键
  13. c语言情话编程,用c语言写的情话
  14. 马云爸爸成立快一年的达摩院,究竟在做什么?
  15. 0x3f3f3f3f是什么意思
  16. lt;html xmlns=http://www.w3.org/1999/xhtmlgt;
  17. 物联网IoT:如何重新定义移动应用开发
  18. laradock、phpstrom、xdebug配置实现断点调试
  19. b站视频-尚硅谷jQuery教程张晓飞老师-笔记
  20. java 屏蔽广告js_用js屏蔽被http劫持的浮动广告实现方法

热门文章

  1. 快!体验文心一言;ChatGPT关键词优化指南;Midjourney从入门到精通;AI绘画资料合集;Midjourney v5效果相当不错 | ShowMeAI日报
  2. 计算机控制系统中常用的过程通道,计算机控制系统3第三章 (1).ppt
  3. 视频号账号被限流是什么原因导致的:国仁楠哥
  4. 阿里达摩院发布的2020十大科技趋势
  5. 机器人 魂斗罗铁血兵团_《魂斗罗·铁血兵团》ENDING
  6. macbook pro链接WiFi成功但是不能上网
  7. 规则_上海证券交易所_债券ETF业务指南(适用单市场ETF)
  8. Two Scoops Django 推荐的数据模型最佳实践
  9. 动物之美计算机教案,岭南版美术四年级下册《14. 成群的动物》教学设计1.doc
  10. 指标搭建篇:北极星指标选择和指标拆解的原则