多线程使用linux时间函数的方法

linux的时间函数有其特别需要注意的使用方法,在工程项目中,这点很容易忽视,本文就时间函数在多线程中的使用作一个小结。

首先看一个函数,取下一天的功能函数,该函数使用了时间函数localtime或者localtime_r来获取系统时间。
int GetNextTime(int curtm)
{
 struct tm t;
 t.tm_year = curtm/10000 - 1900;
 t.tm_mon = ((curtm/100)%100 - 1);
 t.tm_mday = (curtm)%100;
 t.tm_hour = 1;
 t.tm_min = 0;
 t.tm_sec = 0;

 time_t nt;
 //struct tm *local;
 struct tm local;
 char buf[16];

 nt = mktime(&t) + 24 * 3600;
 //local = localtime(&nt); // ------------------(1)
 localtime_r(&nt, &local); // ------------------(2)
 sprintf(buf, "%4d%02d%02d%02d%02d%02d", local.tm_year+1900,local.tm_mon+1,local.tm_mday,0,0,0);
 
 return (int)(atol(buf)/1000000);
}

接着,将函数封装在线程类中,以方便测试多线程使用时间函数localtime或者localtime_r。
class mythread : public Thread
{
 public:
  mythread():m_isRun(false) {}
  virtual void Run()
  {
   int begin = 19860101;
   int end = 20130104;
   int count = 0;
   int cur = begin;
   int next;
   while(cur != end)
   {
    next = GetNextTime(cur);
    count++;
    cur = next;
   }
   if(count != 9865) // 19860101-20130104理论上一共有9865天,如果多线程算出的不是,则打印出来
    cout << count << endl;
  }

  virtual void Stop(){m_isRun=true;}
  virtual ~mythread() {}
 private:
  bool m_isRun;
};

测试:
void threadtest()
{
 for(int k=0;k<20;k++)
 {
  mythread p[30];
  for(int i = 1;i<30;i++)
    p[i].Start();
  for(int i = 1;i<30;i++)
    p[i].Join();
  for(int i = 1;i<30;i++)
    p[i].Stop();
  cout << "the " << k << " test end!" << endl;
 }
}

结果可见,如果时间函数用(1)则,开多线程调用就会报出count不是正确的数量;而用(2)则正确。因此,linux时间函数中——
localtime函数只能用于单线程;而多线程中应该选择用localtime_r。后者是线程安全的时间函数。

mktime,localtime_r,gettimeofday是线程安全的吗?

一下摘自:http://hi.baidu.com/pkuyikai/item/aad084ca252966d797445246

之前为了诊断系统的检索性能中的问题,分阶段地对程序进行了计时,诊断出的结果是:记录越多,那么这三个阶段所消耗的时间就越长。而反复review三个阶段的主要工作代码,却发现这些代码根本不可能这么耗时。慢慢地,顺藤摸瓜地查出时间消耗都发生在获取时间值的方法——mktime()上。于是大家一致怀疑是mktime()方法以及相关的localtime_r()方法都不是线程安全的。可其他项目中也用这些方法,为什么他们就没有发现这样的问题呢?

在glibc的文档描述中,glibc提供了一个线程安全的方法localtime_r来代替localtime。mktime不存在线程不安全的问题。所以,按照glibc的文档,在多线程环境下可以安全的使用localtime_r和mktime,实际情况并非如此。找来mktime()和localtime_r()的源代码,终于发现了奥妙所在。

mktime和localtime_r在实现上都考虑了时区的转换,而时区的计算要使用全局变量tzname/timezone/daylight。这本质上就是线程不安全的。

通过对glibc中这两个系统函数源代码的分析可以知道,它们实现中有两个问题:

1、tzset_internal 中使用的static变量is_initialized

2、mktime每次都要重写全局变量tzname/timezone/daylight

所以mktime和localtime_r不适合于多线程应用。

 

解决方案有二:

1、自己实现mktime和localtime_r,但是这样时区的计算是麻烦的,当然也可以不使用时区信息,或者使用固定时区,比如北京时区,这样就简单多了。由于公司的服务器基本不会有时区问题,因此我们也自己实现了这两个方法。

2、用pthread的mutex来给mktime和localtime_r加锁,但是这样要使用pthread库,移植性不够好。


    gettimeofday()这个syscall用来供用户获取timeval格式的当前时间信息(精确度为微秒级),以及系统的当前时区信息(timezone)。结构类型 timeval的指针参数tv指向接受时间信息的用户空间缓冲区,参数tz是一个timezone结构类型的指针,指向接收时区信息的用户空间缓冲区。这 两个参数均为输出参数,返回值0表示成功,返回负值表示出错。
   

首先来看一下spin_lock机制。spin_lock机制和semaphore机制解决的都是两个进程的互斥问题,都是让一个进程退出临界区后另一个进程才进入的方法,不过sempahore机制实行的是让进程 暂时让出cpu,进入等待队列等待的策略,而spin_lock实行的却是却进程在原地空转,等着另一个进程结束的策略。

    gettimeofday()代码中的read_lock_irqsave(lock, flags)以及read_lock_irq(lock), read_lock_bh(lock) 和 write_lock_irqsave(lock, flags) , write_lock_irq(lock), write_lock_bh(lock)都是spin_lock的一个小小的变型,而Spin_lock采用的方式是让一个进程运行,另外的进程忙等待,由于在只有一个cpu的机器(UP)上微观上只有一个进程在运行。因此gettimeofday()也不是线程安全的系统调用。

你可能感兴趣的:(多线程使用linux时间函数的方法)