Localtime函数的死锁风险

电脑杂谈  发布时间:2020-05-21 14:26:38  来源:网络整理

java线程池安全停止_localtime 线程安全_线程非安全

本文分析了Localtime函数的实现及其可能的死锁风险

前文综述: 企业的日常生活

------------------------------------

(1) 函数定义

struct tm *localtime(const time_t *t);复制代码

(2) 函数描述: 将1970.01.01 00:00:00的秒数转换为现实世界中使用的当前时间和日期

java线程池安全停止_线程非安全_localtime 线程安全

(3) 返回值: 返回指向结构TM的指针,表示当前本地时间

int main(int argc, char *argv[]) {    
    time_t t0 = time(NULL);    
    time_t t1 = t0 + 1800;   
    struct tm* tm1 = localtime(&t0);    
    struct tm* tm2 = localtime(&t1);     
    char str1[50] = {0};    
    char str2[50] = {0};   
     
    strftime(str1, 50, "%H:%M:%S", tm1);    
    strftime(str2, 50, "%H:%M:%S", tm2);  
    printf("%s\n", str1);    
    printf("%s\n", str2);  
    return 0; 
    }复制代码

假设当前运行时间是22:00:26,那么上述代码的结果是什么?

[xxxx@xx-xxx-xxxx-xx00 test]$ gcc localtime_test.c -o test 
[xxxx@xx-xxx-xxxx-xx00 test]$ ./test
22:30:26 
22:30:26复制代码

两个输出时间实际上是一样的,而且它们之间的间隔不是半小时. 假设对Localtime的第二次调用的返回值包含TM1的值,并且可以在内部使用TM type的全局变量. 检查Localtime函数的代码如下:

/* The C Standard says that localtime and gmtime return the same pointer. */
struct tm _tmbuf;
.....
/* Return the `struct tm' representation of *T in local time. */
struct tm * localtime (const time_t *t) {  
return __tz_convert (t, 1, &_tmbuf); 
}复制代码

线程非安全_localtime 线程安全_java线程池安全停止

如您所见,Localtime调用convert\Tmbuf传入的第三个参数是一个全局变量,convert的返回值是指向Tmbuf的指针. 返回指针指向的全局变量可能被另一个调用Localtime的线程重写. 因此,Localtime不是线程安全的. 应该使用线程安全的本地时间,用R函数代替它. 本地时间代码如下:

static struct tm * 
localtime_r (const time_t *t, struct tm *tp) 
{  
  struct tm *l = localtime (t);  
  if (! l)    
   return 0;   
  *tp = *l;  
  return tp; 
  }复制代码

因此,Localtime\R在内部调用Localtime. 但是,每次调用后,返回的结果会立即填充到第二个参数TP指向的内存中,然后返回TP. 因此,函数线程是可重入的

虽然在多线程处理中,调用Localtime是线程安全的,但是性能可能会受到影响. 例如,在输出日志输出时,需要使用Localtime_ur获取当前时间,当多线程并发调用时,锁等待很容易发生,导致性能下降. 对于convert的调用,锁来自_uuz_u,此函数的部分实现如下:

struct tm * 
__tz_convert (const time_t *timer, int use_localtime, struct tm *tp) 
{  
  long int leap_correction;  
  int leap_extra_secs;    
   ......   
    
  __libc_lock_lock (tzset_lock);   //加锁   
   ...... //时间转换逻辑   
  __libc_lock_unlock (tzset_lock); //释放锁     
   ...... 
  return tp; 
 }复制代码

localtime是线程安全的,但在以下两种情况下不安全,甚至会导致死锁

java线程池安全停止_localtime 线程安全_线程非安全

(1) 信号处理函数调用Localtime: 如果进程调用Localtime,则全局锁已获取且尚未释放. 此时,如果进程接收到信号并在信号处理函数中调用Localtime,将导致死锁

(2) 多线程下的Fork: 在多线程下,如果线程a调用Localtime,则全局锁已获得,锁尚未释放. 此时,如果线程B调用forklocaltime 线程安全,并且子进程中也调用了local time,也会导致死锁,导致子进程一直挂起. 因为fork中的子进程只会复制调用它的线程,而其他线程不会复制到子进程中执行,也就是说,只有线程B在当前子进程中运行. 子进程复制父进程的用户空间数据,包括锁信息

Redis的日志输出函数redislograw也调用Localtime来获取时间. 同时,redis还将拥有多线程fork. 那么,redis会有前面提到的性能问题和死锁问题吗?

在5.0.0之前,redis确实使用Localtime进行时间转换. 然而,因为redis是一个单线程架构,所以没有竞争. 因此,一般来说,多线程调用竞争不会导致性能下降或线程不安全

我们说redis是单行的,这意味着它在一个线程中处理所有的请求. 事实上,redis仍然有后台线程来异步执行一些任务. 到目前为止,redis有三个后台线程,功能如下:

在某些情况下,redis会让这些线程在后台完成一些任务. 因此,仍然存在上述僵局的风险. 由于阻塞获取时间转换的方法会导致死锁,因此需要一个非锁定、非阻塞的函数来替换Localtime. 在版本5.0.0中,实现了这样一个函数——nolocks_uuLocalTimelocaltime 线程安全,具体实现如下:

线程非安全_localtime 线程安全_java线程池安全停止

void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) {
     const time_t secs_min = 60;     
     const time_t secs_hour = 3600;     
     const time_t secs_day = 3600*24;      
     
     t -= tz;                           /* Adjust for timezone. */     
     t += 3600*dst;                     /* Adjust for daylight time. */     
     time_t days = t / secs_day;        /* Days passed since epoch. */     
     time_t seconds = t % secs_day;     /* Remaining seconds. */      
     
     tmp->tm_isdst = dst;    
     tmp->tm_hour = seconds / secs_hour;    
     tmp->tm_min = (seconds % secs_hour) / secs_min;    
     tmp->tm_sec = (seconds % secs_hour) % secs_min;    
     
     /* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure * where sunday = 0, so to calculate the day of the week we have to add 4 * and take the modulo by 7. */     
     tmp->tm_wday = (days+4)%7;    
     /* Calculate the current year. */     
     tmp->tm_year = 1970;    
     while(1) {        
          /* Leap years have one day more. */         
          time_t days_this_year = 365 + is_leap_year(tmp->tm_year);        
          if (days_this_year > days) break;         
          days -= days_this_year;        
          tmp->tm_year++;    
   }    
   tmp->tm_yday = days;  /* Number of day of the current year. */
  
    /* We need to calculate in which month and day of the month we are. To do * so we need to skip days according to how many days there are in each * month, and adjust for the leap year that has one more day in February. */     
    int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};    
    mdays[1] += is_leap_year(tmp->tm_year);    
    
    tmp->tm_mon = 0;   
    while(days >= mdays[tmp->tm_mon]) {        
    days -= mdays[tmp->tm_mon];        
    tmp->tm_mon++;     
    }    
    
    tmp->tm_mday = days+1;  /* Add 1 since our 'days' is zero-based. */     
    tmp->tm_year -= 1900;   /* Surprisingly tm_year is year-1900. */
}复制代码

nolocks\uLocalTime的功能与Localtime相同,类型T的时间戳转换为包含年、年、日的TM类型,但它是非阻塞、非锁定和线程安全的. Fork在多线程下也是安全的. 缺点是需要实现时间的转换逻辑

(1) 在实现日志输出函数或接口时,时间转换应尽量避免使用Localtime或Localtime函数,特别是在多线程调用环境中,可能会影响程序的性能. 可以考虑使用全局时间变量让线程定期更新时间变量,而其他线程直接读取时间变量. Redis也有类似的用法,调用updateCacheTime函数来更新serverCron函数中的全局UNIX时间(默认情况下每秒调用10次)

(2) 在信号处理函数或多线程fork()中,尽量避免使用持有全局锁的函数来避免死锁

本文从“小米经营维护”的官方账户入手. 单击可查看原始文本

以上是小编给您介绍的Localtime函数死锁风险. 我希望它能帮助你. 如果您有任何问题,请留言给我,小编会及时回复您的. 非常感谢您的支持!


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-217810-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      • 牛倩倩
        牛倩倩

        地产商不行贿能拿到地吗

      热点图片
      拼命载入中...