近期測试我们自己改进的redis,发如今做rdb时,子进程会一直hang住。gdb attach上。堆栈例如以下:

  1. (gdb) bt
  2. #0 0x0000003f6d4f805e in __lll_lock_wait_private () from /lib64/libc.so.6
  3. #1 0x0000003f6d49dcad in _L_lock_2164 () from /lib64/libc.so.6
  4. #2 0x0000003f6d49da67 in __tz_convert () from /lib64/libc.so.6
  5. #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
  6. #4 0x0000000000421256 in redisLog (level=2, fmt=0x4eedcf "[INFQ_INFO]: %s") at redis.c:363
  7. #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
  8. #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
  9. #7 0x00000000004b37f8 in dump_push_queue (infq=0x17d07f0) at infq.c:1480
  10. #8 0x00000000004b1566 in infq_dump (infq=0x17d07f0, buf=0x7fff9f413650 "", buf_size=1024, data_size=0x7fff9f413a5c) at infq.c:720
  11. #9 0x00000000004440e6 in rdbSaveObject (rdb=0x7fff9f413c50, o=0x7f8c0b4d0470) at rdb.c:600
  12. #10 0x000000000044429c in rdbSaveKeyValuePair (rdb=0x7fff9f413c50, key=0x7fff9f413b90, val=0x7f8c0b4d0470, expiretime=-1, now=1434687031023) at rdb.c:642
  13. #11 0x0000000000444471 in rdbSaveRio (rdb=0x7fff9f413c50, error=0x7fff9f413c4c) at rdb.c:686
  14. #12 0x0000000000444704 in rdbSave (filename=0x7f8c0b410040 "dump.rdb") at rdb.c:750
  15. #13 0x00000000004449cd in rdbSaveBackground (filename=0x7f8c0b410040 "dump.rdb") at rdb.c:831
  16. #14 0x0000000000422b0e in serverCron (eventLoop=0x7f8c0b45a150, id=0, clientData=0x0) at redis.c:1240
  17. #15 0x000000000041d47e in processTimeEvents (eventLoop=0x7f8c0b45a150) at ae.c:311
  18. #16 0x000000000041d7c0 in aeProcessEvents (eventLoop=0x7f8c0b45a150, flags=3) at ae.c:423
  19. #17 0x000000000041d8de in aeMain (eventLoop=0x7f8c0b45a150) at ae.c:455
  20. #18 0x0000000000429ae3 in main (argc=2, argv=0x7fff9f414168) at redis.c:3843

都堵塞在redisLog上。用于打印日志。在打印日志时,须要调用localtime生成时间。

查看glibc代码glibc-2.9/time/localtime.c:

  1. /* Return the `struct tm' representation of *T in local time,
  2. using *TP to store the result. */
  3. struct tm *
  4. __localtime_r (t, tp)
  5. const time_t *t;
  6. struct tm *tp;
  7. {
  8. return __tz_convert (t, 1, tp);
  9. }
  10. weak_alias (__localtime_r, localtime_r)
  11.  
  12. /* Return the `struct tm' representation of *T in local time. */
  13. struct tm *
  14. localtime (t)
  15. const time_t *t;
  16. {
  17. return __tz_convert (t, 1, &_tmbuf);
  18. }
  19. libc_hidden_def (localtime)

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

  1. /* This locks all the state variables in tzfile.c and this file. */
  2. __libc_lock_define_initialized (static, tzset_lock)
  3.  
  4. /* Return the `struct tm' representation of *TIMER in the local timezone.
  5. Use local time if USE_LOCALTIME is nonzero, UTC otherwise. */
  6. struct tm *
  7. __tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
  8. {
  9. long int leap_correction;
  10. int leap_extra_secs;
  11.  
  12. if (timer == NULL)
  13. {
  14. __set_errno (EINVAL);
  15. return NULL;
  16. }
  17.  
  18. // 加锁
  19. __libc_lock_lock (tzset_lock);
  20.  
  21. // 一些出来逻辑
  22.  
  23. // 解锁
  24. __libc_lock_unlock (tzset_lock);
  25.  
  26. return tp;
  27. }

这个函数是用的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加解锁,达到一致状态。

  1. #include <pthread.h>
  2.  
  3. int
  4. pthread_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. 缺陷的背后(四)---多进程之for循环下fork子进程引发bug

    导语 业务模块为实现高并发时的更快的处理速度,经常会采用多进程的方式去处理业务.多进程模式下常见的三种bug:for循环下fork子进程导致产生无数孙子进程,僵尸进程,接口窜包.本章主要介绍第一种常见 ...

  2. 第10章 线程控制(5)_多线程下的fork

    6. 线程和fork 6.1 多线程下的fork (1)历史包袱 ①fork与多线程的协作性很差,这是POSIX系统操作系统的历史包袱. ②长期以来程序都是单线程的,fork运行正常,但引入线程这后, ...

  3. ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开

    ASP.NET MVC Filters 4种默认过滤器的使用[附示例]   过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...

  4. 多线程中fork与mutex

    在多线程程序中fork出一个新进程,发现新的进程无法正常工作.因为:在使用fork时会将原来进程中的所有内存数据复制一份保存在子进程中.但是在拷贝的时候,但是线程是无法被拷贝的.如果在原来线程中加了锁 ...

  5. 【转】Linux下Fork与Exec使用

    Linux下Fork与Exec使用 转自 Linux下Fork与Exec使用 一.引言 对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值.for ...

  6. Linux下Fork与Exec使用

    Linux下Fork与Exec使用   一.引言 对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值.fork函数是Unix系统最杰出的成就之一, ...

  7. [转帖]Linux下fork函数及pthread函数的总结

    Linux下fork函数及pthread函数的总结 https://blog.csdn.net/wangdd_199326/article/details/76180514 fork Linux多进程 ...

  8. Java多线程21:多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask

    CyclicBarrier 接着讲多线程下的其他组件,第一个要讲的就是CyclicBarrier.CyclicBarrier从字面理解是指循环屏障,它可以协同多个线程,让多个线程在这个屏障前等待,直到 ...

  9. 【Linux下进程机制】从一道面试题谈linux下fork的运行机制

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

随机推荐

  1. pyspark import 可以通过 --py-files

    公用函数的放到了 common.py 文件中. 通过 --py-files 可以在pyspark中可以顺利导入: pyspark --py-files lib/common.py > impor ...

  2. Struts2国际化-getText()方法

    转自https://blog.csdn.net/qq_43560838/article/details/83747604 一:简单理解 国际化简称i18n,其来源是英文单词 international ...

  3. 在Ubuntu下使用命令删除目录

    在Ubuntu命令行中用命令删除目录,现在在Linux系统中删除目录大致会用两个,rm和rmdir,rm命令删除目录很简单,不过很多人还是比较习惯用rmdir命令,如果操作的目录非空时就有点麻烦.这时 ...

  4. Spring MVC模式示例(采用解耦控制器)

    Product package com.mstf.bean; import java.io.Serializable; /** * Product类,封装了一些信息,包含三个属性 * @author ...

  5. UI Framework-1: Aura and Shell dependencies

    Aura and Shell dependencies The diagram below shows the dependencies of Chrome, Ash (Aura shell), vi ...

  6. appium使用教程(一 环境搭建)-------------1.准备阶段

    1.准备: 下载需要的安装文件等. 1) Appium(由于某种原因,官网下载会下载一半就挂掉: http://appium.io/).建议去网盘下载 2) . net framework 4.0  ...

  7. HDU 4960 Another OCD Patient 简单DP

    思路: 因为是对称的,所以如果两段是对称的,那么一段的前缀和一定等于另一段的后缀和.根据这个性质,我们可以预处理出这个数列的对称点对.然后最后一个对称段是从哪里开始的,做n^2的DP就可以了. 代码: ...

  8. Android ImageView设置图片原理(下)

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 写完上一篇后,总认为介绍的知识点不多,仅仅是一种在UI线程解析载入图片.两种在子线程解析,在UI线程 ...

  9. leetCode 36.Valid Sudoku(有效的数独) 解题思路和方法

    Valid Sudoku Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules. The Sudoku bo ...

  10. web前端开发——AJAX入门

    什么是AJAX AJAX: A New Approach to Web Applications XML AJAX是老技术新思想. 它所包括的内容我们之前都接触过.例如以下: (1)使用XHTML和C ...