从pthread_time_timewait引出Unix中的时间类型

pthread_cond_timewait 是线程同步函数,函数原型如下:

#include <pthread.h>

int pthread_cond_timewait(pthread_cond_t  *restrict cond, 
						  pthread_mutex_t  *restrict mutex,
						  const struct timespec  *restrict abstime);

int pthread_cond_wait(pthread_cond_t  *restrict cond,
					  pthread_mutex_t  *restrict mutex);

pthread_cond_timewait 表示的是在经过 abstime 的时间后如果仍无事件发生,则超时返回,那么,struct timespec 是一种什么样的数据类型?该如何使用?


Unix中的时间类型:

在很多场景下都会需要用到Unix的时间戳,例如调试日志的时间打印、阻塞型系统的调用的等待时间参数等,因此有必要了解其使用方法。

Unix中的时间类型按照时间精度可分为三种:
(1)time_t (秒 seconds)
(2)struct timeval(微秒 micro-seconds)
(3)struct timespec(纳秒 nano-seconds)


1. time_t :

time系列函数涉及到三种数据类型:
time_tstruct tm *char *

time_t 类型是一个32位或64位的二进制整数(可以理解为int型),直接打印并不能获取到时间,需要对time_t进行转换,这里有两种方式:

  1. 第一种方式是先将 time_t 转换为 struct tm * 类型的结构体,再将结构体转换为 char * 类型的字符串进行输出,用到的函数是 localtime()asctime()
  2. 第二种方式是将 time_t 直接转换为 char * 类型,使用的函数是 ctime();

函数原型:

#include <time.h>
time_t time(time_t *tloc);     //time函数的返回值是从1970年1月1日到现在的秒数
#include <time.h>
struct tm* gmtime(const time_t *timep);
struct tm* localtime(const time_t *timep);

struct tm {
    
    
	int		tm_sec;
	int		tm_min;
	int		tm_hour;
	int		tm_mday;
	int		tm_mon;
	int		tm_year;
	
	int		tm_wday;
	int		tm_yday;
	int		tm_isdst;
};
#include <time.h>
time_t mktime(struct tm* tm);		//反向操作:将一个 struct tm * 类型转换为 time_t 类型
#include <time.h>
char *asctime(const struct tm* timeptr);
#include <time.h>
char *ctime(const time_t *timep);

使用举例:

第一种形式:(time_t) ⇒ (char*)

#include <time.h>
#include <stdio.h>

int main() {
    
    
	time_t timep;
	time(&timep);

	char *ret = ctime(&timep);
	printf("%s\n", ret);

	return 0;
}

输出结果:

Sun Jan  3 12:37:47 2021

第二种形式:(time_t) ⇒ (struct tm *) ⇒ (char *)

#include <time.h>
#include <stdio.h>

int main() {
    
    

	time_t timep;
	time(&timep);

	char *ret = asctime(localtime(&timep));
	printf("%s\n", ret);
	
	return 0;
}

输出结果:

Sun Jan  3 12:37:17 2021

2. struct timeval :

timeval 是获取 微秒(microseconds) 级别的时间。

#include <sys/time.h>

struct timeval {
    
    
	time_t			tv_sec;		/* seconds */
	suseconds_t		tv_usec;	/* micro-seconds */
};

函数原型:

#include <sys/time.h>

//获取系统时间
int gettimeofday(struct timeval *tv, struct timezone *tz);	

//设置系统时间
int settimeofday(const struct timeval *tv, const struct timeval *tz);	

第二个参数 timezone* 表示时区,一般情况下不使用,传入NULL即可。

使用举例:

#include <sys/time.h>
#include <stdio.h>

int main() {
    
    

	struct timeval ret;
	gettimeofday(&ret, NULL);
	printf("sec: %d, msec: %d\n", ret.tv_sec, ret.tv_usec);

	return 0;
}

输出结果:

secc: 1609655357, msec: 192692

3. struct timespec :

struct timespec 是获取纳秒级别(nano-seconds)的时间类型。

函数原型:

#include <time.h>

struct timespec {
    
    
	time_t		tv_sec;		/* seconds */
	long		tv_nsec;	/* nano-seconds */
};
#include <time.h>

int clock_gettime(clockid_t clk_id, struct timespec *res);
int clock_settime(clockid_t clk_id, const struct timespec *tp);

参数clockid_t 表示时钟类型,Linux提供下面这些时钟类型:

CLOCK_REALTIME			//从1970年1月1日至今的时间
CLOCK_REALTIME_COARSE
CLOCK_MONOTONIC	//从系统启动到现在所流逝的时间,它是单调递增的,CPU每tick一次则计数器+1
CLOCK_MONOTONIC_COARSE
CLOCK_BOOTTIME
CLOCK_PROCESS_CPUTIME_ID
CLOCK_TRHEAD_CPUTIME_ID

使用举例:

#include <time.h>
#include <stdio.h>

int main() {
    
    
	struct timespec res;
	
	clock_gettime(CLOCK_REALTIME, &res);
	printf("seconds: %ld, nano-seconds: %lu\n", res.tv_sec, res.tv_nsec);
	return 0;
}

输出结果:

seconds: 1609656874, nano-seconds: 434777000

在 pthread_cond_timewait 中 timespec 使用:

void *workerThread(void *arg) {
    
    
	
	struct timespec ts;
	clock_gettime(CLOCK_REALTIME, &ts);
	ts->tv_sec += pool->linger;
	
	if(pthread_cond_timewait(&cond, &mutex, &ts) == ETIMEOUT) {
    
    
		//pthread_exit();
	}
}

设置 cond 的超时时间,就是 clock_gettime 先获取当前时间,然后加上超时等待时间后,再传入到 pthread_cond_timewait 中。


localtime 与localtime_r : 线程安全 与 非线程安全:

char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf); //返回值保存在*buf中

char *ctime(const time_t *time_p);
char *ctime_r(const time_t *time_p, char *buf);

struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

为什么 localtime 不是线程安全的,而 localtime_r 是线程安全的?

localtime的使用方式是这样的:

struct tm *res = localtime(&timep);  //将time_t类型的timep转换为 struct tm * 类型

可以看到只需要一个指针 *res,而localtime的返回值必定是保存在一块内存中的,但是我们并没有申请内存,这是因为localtime函数自动的申请了一块内存,*res指针指示指向了这块我们看不到的内存(在内核管理)。
此时,如果有另一个线程也调用了 localtime函数,那么这块保存时间的“隐式”内存中的值就会被覆盖,*res中的值就会不准确,此为“非线程安全”;

而localtime_r(&timep, &result); 是把返回值保存在参数result中,这块内存是需要用户手动分配的,此为“线程安全”。

==> 由此引申出“线程安全”的一个注意事项:
当需要调用到系统调用时,不同的线程应各自申请内部保存各自的返回值。


参考内容:

《C++时间类型详解 time_t》
https://www.runoob.com/w3cnote/cpp-time_t.html

《Linux时间类型、函数和休眠函数》
https://blog.csdn.net/luotuo44/article/details/39374759

《localtime、localtime_s、localtime_r的使用》
https://blog.csdn.net/u010087712/article/details/50731222

猜你喜欢

转载自blog.csdn.net/ArtAndLife/article/details/111876639