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()也不是线程安全的系统调用。