localtime/localtime_r lock issue

来源:互联网 发布:阿里云增值税专用发票 编辑:程序博客网 时间:2024/06/06 07:47
看了localtime/localtim_r的代码,里面确实有lock的操作,但有新发现。

在glibc的代码目录,time/localtime.c中,是localtime/localtime_r的实现:

struct tm _tmbuf;


/* Return the `struct tm' representation of *T in local time,
using *TP to store the result. */
struct tm *
__localtime_r (t, tp)
const time_t *t;
struct tm *tp;
{
return __tz_convert (t, 1, tp);
}
weak_alias (__localtime_r, localtime_r)


/* Return the `struct tm' representation of *T in local time. */
struct tm *
localtime (t)
const time_t *t;
{
return __tz_convert (t, 1, &_tmbuf);
}

从这几行代码看,是否reentrant,对比很明显。

接着再看time/tzset.c中__tz_convert函数的代码:

/* Return the `struct tm' representation of *TIMER in the local timezone.
Use local time if USE_LOCALTIME is nonzero, UTC otherwise. */
struct tm *
__tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
{
long int leap_correction;
int leap_extra_secs;

if (timer == NULL)
{
__set_errno (EINVAL);
return NULL;
}

__libc_lock_lock (tzset_lock);

/* Update internal database according to current TZ setting.
POSIX.1 8.3.7.2 says that localtime_r is not required to set tzname.
This is a good idea since this allows at least a bit more parallelism. */
tzset_internal (tp == &_tmbuf && use_localtime, 1);

if (__use_tzfile)
__tzfile_compute (*timer, use_localtime, &leap_correction,
&leap_extra_secs, tp);
else
{
if (! __offtime (timer, 0, tp))
tp = NULL;
else
__tz_compute (*timer, tp, use_localtime);
leap_correction = 0L;
leap_extra_secs = 0;
}

if (tp)
{
if (! use_localtime)
{
tp->tm_isdst = 0;
tp->tm_zone = "GMT";
tp->tm_gmtoff = 0L;
}

if (__offtime (timer, tp->tm_gmtoff - leap_correction, tp))
tp->tm_sec += leap_extra_secs;
else
tp = NULL;
}

__libc_lock_unlock (tzset_lock);

return tp;
}

其中进行了成对的加解锁操作,对tzset_lock这个变量。再看tzset_lock的声明和注释:

/* This locks all the state variables in tzfile.c and this file. */
__libc_lock_define_initialized (static, tzset_lock)

因为tzset_lock可以对两个文件中的变量都起作用,所以可以推出,需要tzset_lock保护的变量,肯定是或者包含于这两个文件的external变量中,同时,也可能包含这两个文件的文件局部变量(static)。

看一下,__tz_convert函数中,哪些操作需要锁住tzset_lock。__tz_convert函数中,从__libc_lock_lock (tzset_lock);语句之后看起。

首先,调用了tzset_internal函数,tzset_internal (tp == &_tmbuf && use_localtime, 1);,tzset_internal函数的实现如下:

/* Interpret the TZ envariable. */
static void
internal_function
tzset_internal (always, explicit)
int always;
int explicit;
{
static int is_initialized;
const char *tz;

if (is_initialized && !always)
return;
is_initialized = 1;

/* Examine the TZ environment variable. */
tz = getenv ("TZ");
if (tz == NULL && !explicit)
/* Use the site-wide default. This is a file name which means we
would not see changes to the file if we compare only the file
name for change. We want to notice file changes if tzset() has
been called explicitly. Leave TZ as NULL in this case. */
tz = TZDEFAULT;
if (tz && *tz == '\0')
/* User specified the empty string; use UTC explicitly. */
tz = "Universal";

/* A leading colon means "implementation defined syntax".
We ignore the colon and always use the same algorithm:
try a data file, and if none exists parse the 1003.1 syntax. */
if (tz && *tz == ':')
++tz;

/* Check whether the value changed since the last run. */
if (old_tz != NULL && tz != NULL && strcmp (tz, old_tz) == 0)
/* No change, simply return. */
return;

if (tz == NULL)
/* No user specification; use the site-wide default. */
tz = TZDEFAULT;

tz_rules[0].name = NULL;
tz_rules[1].name = NULL;

/* Save the value of `tz'. */
free (old_tz);
old_tz = tz ? __strdup (tz) : NULL;

/* Try to read a data file. */
__tzfile_read (tz, 0, NULL);
if (__use_tzfile)
return;

/* No data file found. Default to UTC if nothing specified. */

if (tz == NULL || *tz == '\0'
|| (TZDEFAULT != NULL && strcmp (tz, TZDEFAULT) == 0))
{
memset (tz_rules, '\0', sizeof tz_rules);
tz_rules[0].name = tz_rules[1].name = "UTC";
if (J0 != 0)
tz_rules[0].type = tz_rules[1].type = J0;
tz_rules[0].change = tz_rules[1].change = (time_t) -1;
update_vars ();
return;
}

__tzset_parse_tz (tz);
}

很明显,tzset_internal函数对多个tzset.c的external变量和文件局部变量进行了读或写的操作,同时还操作了进程的环境变量TZ。

接下来,__tz_convert函数对tzfile.c的external变量__use_tzfile进行了读操作。

根据__use_tzfile变量的值不同,接下来分别调用了__tzfile_compute函数或者__tz_compute函数。

void
__tzfile_compute (time_t timer, int use_localtime,
long int *leap_correct, int *leap_hit,
struct tm *tp)
{
size_t i;

if (use_localtime)
{
__tzname[0] = NULL;
__tzname[1] = NULL;

if (__builtin_expect (num_transitions == 0 || timer < transitions[0], 0))
{
/* TIMER is before any transition (or there are no transitions).
Choose the first non-DST type
(or the first if they're all DST types). */
i = 0;
while (i < num_types && types[i].isdst)
{
if (__tzname[1] == NULL)
__tzname[1] = __tzstring (&zone_names[types[i].idx]);

++i;
}

if (i == num_types)
i = 0;
__tzname[0] = __tzstring (&zone_names[types[i].idx]);
if (__tzname[1] == NULL)
{
size_t j = i;
while (j < num_types)
if (types[j].isdst)
{
__tzname[1] = __tzstring (&zone_names[types[j].idx]);
break;
}
else
++j;
}
}
else if (__builtin_expect (timer >= transitions[num_transitions - 1], 0))
{
if (__builtin_expect (tzspec == NULL, 0))
{
use_last:
i = num_transitions;
goto found;
}

/* Parse the POSIX TZ-style string. */
__tzset_parse_tz (tzspec);

/* Convert to broken down structure. If this fails do not
use the string. */
if (__builtin_expect (! __offtime (&timer, 0, tp), 0))
goto use_last;

/* Use the rules from the TZ string to compute the change. */
__tz_compute (timer, tp, 1);

/* If tzspec comes from posixrules loaded by __tzfile_default,
override the STD and DST zone names with the ones user
requested in TZ envvar. */
if (__builtin_expect (zone_names == (char *) &leaps[num_leaps], 0))
{
assert (num_types == 2);
__tzname[0] = __tzstring (zone_names);
__tzname[1] = __tzstring (&zone_names[strlen (zone_names) + 1]);
}

goto leap;
}
else
{
/* Find the first transition after TIMER, and
then pick the type of the transition before it. */
size_t lo = 0;
size_t hi = num_transitions - 1;
/* Assume that DST is changing twice a year and guess initial
search spot from it.
Half of a gregorian year has on average 365.2425 * 86400 / 2
= 15778476 seconds. */
i = (transitions[num_transitions - 1] - timer) / 15778476;
if (i < num_transitions)
{
i = num_transitions - 1 - i;
if (timer < transitions[i])
{
if (i < 10 || timer >= transitions[i - 10])
{
/* Linear search. */
while (timer < transitions[i - 1])
--i;
goto found;
}
hi = i - 10;
}
else
{
if (i + 10 >= num_transitions || timer < transitions[i + 10])
{
/* Linear search. */
while (timer >= transitions[i])
++i;
goto found;
}
lo = i + 10;
}
}

/* Binary search. */
/* assert (timer >= transitions[lo] && timer < transitions[hi]); */
while (lo + 1 < hi)
{
i = (lo + hi) / 2;
if (timer < transitions[i])
hi = i;
else
lo = i;
}
i = hi;

found:
/* assert (timer >= transitions[i - 1]
&& (i == num_transitions || timer < transitions[i])); */
__tzname[types[type_idxs[i - 1]].isdst]
= __tzstring (&zone_names[types[type_idxs[i - 1]].idx]);
size_t j = i;
while (j < num_transitions)
{
int type = type_idxs[j];
int dst = types[type].isdst;
int idx = types[type].idx;

if (__tzname[dst] == NULL)
{
__tzname[dst] = __tzstring (&zone_names[idx]);

if (__tzname[1 - dst] != NULL)
break;
}

++j;
}

if (__builtin_expect (__tzname[0] == NULL, 0))
__tzname[0] = __tzname[1];

i = type_idxs[i - 1];
}

struct ttinfo *info = &types[i];
__daylight = rule_stdoff != rule_dstoff;
__timezone = -rule_stdoff;

if (__tzname[0] == NULL)
{
/* This should only happen if there are no transition rules.
In this case there should be only one single type. */
assert (num_types == 1);
__tzname[0] = __tzstring (zone_names);
}
if (__tzname[1] == NULL)
/* There is no daylight saving time. */
__tzname[1] = __tzname[0];
tp->tm_isdst = info->isdst;
assert (strcmp (&zone_names[info->idx], __tzname[tp->tm_isdst]) == 0);
tp->tm_zone = __tzname[tp->tm_isdst];
tp->tm_gmtoff = info->offset;
}

leap:
*leap_correct = 0L;
*leap_hit = 0;

/* Find the last leap second correction transition time before TIMER. */
i = num_leaps;
do
if (i-- == 0)
return;
while (timer < leaps[i].transition);

/* Apply its correction. */
*leap_correct = leaps[i].change;

if (timer == leaps[i].transition && /* Exactly at the transition time. */
((i == 0 && leaps[i].change > 0) ||
leaps[i].change > leaps[i - 1].change))
{
*leap_hit = 1;
while (i > 0
&& leaps[i].transition == leaps[i - 1].transition + 1
&& leaps[i].change == leaps[i - 1].change + 1)
{
++*leap_hit;
--i;
}
}
}


/* Figure out the correct timezone for TM and set `__tzname',
`__timezone', and `__daylight' accordingly. */
void
internal_function
__tz_compute (timer, tm, use_localtime)
time_t timer;
struct tm *tm;
int use_localtime;
{
compute_change (&tz_rules[0], 1900 + tm->tm_year);
compute_change (&tz_rules[1], 1900 + tm->tm_year);

if (use_localtime)
{
int isdst;

/* We have to distinguish between northern and southern
hemisphere. For the latter the daylight saving time
ends in the next year. */
if (__builtin_expect (tz_rules[0].change
> tz_rules[1].change, 0))
isdst = (timer < tz_rules[1].change
|| timer >= tz_rules[0].change);
else
isdst = (timer >= tz_rules[0].change
&& timer < tz_rules[1].change);
tm->tm_isdst = isdst;
tm->tm_zone = __tzname[isdst];
tm->tm_gmtoff = tz_rules[isdst].offset;
}
}

非常明显,__tzfile_compute函数和__tz_compute函数都对多个需要保护的变量进行了操作。

如果__tz_convert函数调用了__tz_compute函数,那么必定调用了__offtime函数(time/offtime.c)。

/* Compute the `struct tm' representation of *T,
offset OFFSET seconds east of UTC,
and store year, yday, mon, mday, wday, hour, min, sec into *TP.
Return nonzero if successful. */
int
__offtime (t, offset, tp)
const time_t *t;
long int offset;
struct tm *tp;
{
time_t days, rem, y;
const unsigned short int *ip;

days = *t / SECS_PER_DAY;
rem = *t % SECS_PER_DAY;
rem += offset;
while (rem < 0)
{
rem += SECS_PER_DAY;
--days;
}
while (rem >= SECS_PER_DAY)
{
rem -= SECS_PER_DAY;
++days;
}
tp->tm_hour = rem / SECS_PER_HOUR;
rem %= SECS_PER_HOUR;
tp->tm_min = rem / 60;
tp->tm_sec = rem % 60;
/* January 1, 1970 was a Thursday. */
tp->tm_wday = (4 + days) % 7;
if (tp->tm_wday < 0)
tp->tm_wday += 7;
y = 1970;

#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))

while (days < 0 || days >= (__isleap (y) ? 366 : 365))
{
/* Guess a corrected year, assuming 365 days per year. */
time_t yg = y + days / 365 - (days % 365 < 0);

/* Adjust DAYS and Y to match the guessed year. */
days -= ((yg - y) * 365
+ LEAPS_THRU_END_OF (yg - 1)
- LEAPS_THRU_END_OF (y - 1));
y = yg;
}
tp->tm_year = y - 1900;
if (tp->tm_year != y - 1900)
{
/* The year cannot be represented due to overflow. */
__set_errno (EOVERFLOW);
return 0;
}
tp->tm_yday = days;
ip = __mon_yday[__isleap(y)];
for (y = 11; days < (long int) ip[y]; --y)
continue;
days -= ip[y];
tp->tm_mon = y;
tp->tm_mday = days + 1;
return 1;
}

也很明显,__offtime函数本身是reentrant和thread-safe的,也就是说,对__offtime函数的调用,不需要任何保护。

接着往下看__tz_convert函数,在调用完__tzfile_compute函数或者__tz_compute函数后,剩下的操作都是调用__offtime函数以及对函数局部变量的操作,所以这些操作其实不需要保护。

所以,我们可以大胆猜测,在__tz_convert函数中,对tzset_lock的解锁操作__libc_lock_unlock (tzset_lock);,可以更早的进行。这样,虽然不能从本质上解决localtime/localtime_r的对锁的竞争的问题,但理论上,可以一定程度上提高调用localtime_r的多线程程序的并发度。

另一个发现是,上面的分析,我是参考了glibc-2.12和glibc-2.19的代码,最后下载了最新的stable relase glibc-2.22的代码,在__tz_convert函数中,对__libc_lock_unlock函数的调用,确实如分析的那样提前了。但很可能生产环境中很少有已经升级到glibc-2.22的。
0 0