A data type or static method is threadsafe if it behaves correctly when used from multiple threads, regardless of how those threads are executed, and without demanding additional coordination from the calling code.
"behaves correctly" means satisfying its specification and preserving its rep invariant;
"regardless of how threads are executed" means threads might be on multiple processors or timesliced on the same processor;
"without additional coordination" means that the data type can't put preconditions on its caller related to timing, like "you can't call get() while set() is in progress."
Remember Iterator? It's not threadsafe. Iterator's specification says that you can't modify a collection at the same time as you're iterating over it. That's a timing-related precondition put on the caller, and Iterator makes no guarantee to behave correctly if you violate it.
C++ classes are often reentrant, simply because they only access their own member data. Any thread can call a member function on an instance of a reentrant class, as long as no other thread can call a member function on the same instance of the class at the same time. For example, the Counter class below is reentrant:
(
翻译: C++ 类通常是可重入的,这仅仅是因为它们只访问自己的成员数据.
只要没有其他线程同时调用该类的同一个实例的成员函数,任何线程都可以调用可重入类的实例的成员函数
例如,下面的 Counter 类就是可重入的:
)
cpp复制代码
class Counter
{
public:
Counter() { n = 0; }
void increment() { ++n; }
void decrement() { --n; }
int value() const { return n; }
private:
int n;
};
The class isn't thread-safe, because if multiple threads try to modify the data member n, the result is undefined. This is because the ++ and -- operators aren't always atomic. Indeed, they usually expand to three machine instructions:
Load the variable's value in a register.
Increment or decrement the register's value.
Store the register's value back into main memory.
If thread A and thread B load the variable's old value simultaneously, increment their register, and store it back, they end up overwriting each other, and the variable is incremented only once!
A reentrant function does not hold static data over successive calls, nor does it return a pointer to static data. All data is provided by the caller of the function. A reentrant function must not call non-reentrant functions.
A non-reentrant function can often, but not always, be identified by its external interface and its usage. For example, the strtok subroutine is not reentrant, because it holds the string to be broken into tokens. The ctime subroutine is also not reentrant; it returns a pointer to static data that is overwritten by each call.
#include <string.h>
/* Parse S into tokens separated by characters in DELIM.
If S is NULL, the last string strtok() was called with is
used. For example:
char s[] = "-abc-=-def";
x = strtok(s, "-"); // x = "abc"
x = strtok(NULL, "-="); // x = "def"
x = strtok(NULL, "="); // x = NULL
// s = "abc\0=-def\0"
*/
char *
strtok (char *s, const char *delim)
{
static char *olds;//static!!!
return __strtok_r (s, delim, &olds);
}
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#ifndef _LIBC
/* Get specification. */
# include "strtok_r.h"
# define __strtok_r strtok_r
#endif
__strtok_r的定义: 在__strtok_r内部没有使用static修饰的静态变量,而是"the saved pointer in SAVE_PTR is used as the next starting point"
cpp复制代码
/* Parse S into tokens separated by characters in DELIM.
If S is NULL, the saved pointer in SAVE_PTR is used as
the next starting point. For example:
char s[] = "-abc-=-def";
char *sp;
x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def"
x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL
x = strtok_r(NULL, "=", &sp); // x = NULL
// s = "abc\0-def\0"
*/
char *
__strtok_r (char *s, const char *delim, char **save_ptr)
{
char *end;
if (s == NULL)
s = *save_ptr;
if (*s == '\0')
{
*save_ptr = s;
return NULL;
}
/* Scan leading delimiters. */
s += strspn (s, delim);
if (*s == '\0')
{
*save_ptr = s;
return NULL;
}
/* Find the end of the token. */
end = s + strcspn (s, delim);
if (*end == '\0')
{
*save_ptr = end;
return s;
}
/* Terminate the token and make *SAVE_PTR point past it. */
*end = '\0';
*save_ptr = end + 1;
return s;
}
#ifdef weak_alias
libc_hidden_def (__strtok_r)
weak_alias (__strtok_r, strtok_r)
#endif
#include <iostream>
#include <ctime>
int main ()
{
time_t curtime=time(nullptr);
time(&curtime);
std::cout<<"当前时间为"<<ctime(&curtime);
return 0;
}
运行结果:
在glibc-2.42的/time/ctime.c中定义了ctime函数
cpp复制代码
/* Copyright (C) 1991-2025 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <time.h>
/* Return a string as returned by asctime which
is the representation of *T in that form. */
char *
__ctime64 (const __time64_t *t)
{
/* The C Standard says ctime (t) is equivalent to asctime (localtime (t)).
In particular, ctime and asctime must yield the same pointer. */
return asctime (__localtime64 (t));
}
/* Provide a 32-bit variant if needed. */
#if __TIMESIZE != 64
libc_hidden_def (__ctime64)
char *
ctime (const time_t *t)
{
__time64_t t64 = *t;
return __ctime64 (&t64);
}
#endif
asctime定义在/time/asctime.c中:
cpp复制代码
/* Returns a string of the form "Day Mon dd hh:mm:ss yyyy\n"
which is the representation of TP in that form. */
char *
asctime (const struct tm *tp)
{
return asctime_internal (tp, result, sizeof (result));
}
libc_hidden_def (asctime)
asctime_internal定义在/time/asctime.c中:
cpp复制代码
static char *
asctime_internal (const struct tm *tp, char *buf, size_t buflen)
{
if (tp == NULL)
{
__set_errno (EINVAL);
return NULL;
}
/* We limit the size of the year which can be printed. Using the %d
format specifier used the addition of 1900 would overflow the
number and a negative value is printed. For some architectures we
could in theory use %ld or an evern larger integer format but
this would mean the output needs more space. This would not be a
problem if the 'asctime_r' interface would be defined sanely and
a buffer size would be passed. */
if (__glibc_unlikely (tp->tm_year > INT_MAX - 1900))
{
eoverflow:
__set_errno (EOVERFLOW);
return NULL;
}
int n = __snprintf (buf, buflen, format,
(tp->tm_wday < 0 || tp->tm_wday >= 7
? "???" : ab_day_name (tp->tm_wday)),
(tp->tm_mon < 0 || tp->tm_mon >= 12
? "???" : ab_month_name (tp->tm_mon)),
tp->tm_mday, tp->tm_hour, tp->tm_min,
tp->tm_sec, 1900 + tp->tm_year);
if (n < 0)
return NULL;
if (n >= buflen)
goto eoverflow;
return buf;
}