11.C库的非线程安全函数

我们先来看一段代码:

#include <time.h>
​
int main()
{
    time_t tNow = time(NULL);
    time_t tEnd = tNow + 1800;
    //注意下面两行的区别
    struct tm* ptm = localtime(&tNow);
    struct tm* ptmEnd = localtime(&tEnd);
​
    char szTmp[50] = { 0 };
    strftime(szTmp, 50, "%H:%M:%S", ptm);
​
    //struct tm* ptmEnd = localtime(&tEnd);
    char szEnd[50] = { 0 };
    strftime(szEnd, 50, "%H:%M:%S", ptmEnd);
    printf("%s \n", szTmp);
    printf("%s \n", szEnd);
    
    return 0;
}

程序执行结果如下所示:

20:53:48
20:53:48

很奇怪是不是?tNow 和 tEnd 明明相差 1800 秒。我们调整一下代码第 9 行的位置:

#include <time.h>
​
int main()
{
    time_t tNow = time(NULL);
    time_t tEnd = tNow + 1800;
    //注意下面两行的区别
    struct tm* ptm = localtime(&tNow);    
​
    char szTmp[50] = { 0 };
    strftime(szTmp, 50, "%H:%M:%S", ptm);
​
    struct tm* ptmEnd = localtime(&tEnd);
    char szEnd[50] = { 0 };
    strftime(szEnd, 50, "%H:%M:%S", ptmEnd);
    printf("%s \n", szTmp);
    printf("%s \n", szEnd);
    
    return 0;
}

这次输出结果正确了:

20:25:44
20:55:44

为什么会出现这种情况呢?我们来看下 localtime 函数的签名:

struct tm* localtime(const time_t* timep);

这个函数返回一个 tm 结构体指针类型,而我们外部并不需要释放这个指针指向的内存,因此我们断定这个函数内部一定使用了一个全局变量或函数内部的静态变量。这样的话,当再次调用这个函数时有可能前一次调用结果就被后一个结果覆盖了。我们简化一下这种模型:

int* func(int k)
{
    static int result;
    result = k;
    return &result;
}

当多个线程甚至单个线程调用这个函数时,如两个线程分别调用上述函数:

//线程1调用
int* p1 = func(1);
//线程2调用
int* p2 = func(2);

那么 *p1 和 *p2 的结果会是什么呢?结论是可能是 1 也可能是 2,甚至既不是 1 也不是 2。原因我们在前面《为什么整形变量赋值操作不是原子》的小节已经介绍过了。

像 localtime 这类 CRT 提供的具有上述行为的函数,我们称为非线程安全函数。因此我们在实际开发中应避免在多线程程序中使用这类函数,这类函数还有如 strtok,甚至连操作系统提供的 socket 函数 gethostbyname 也不是线程安全的。

char* strtok(char* str, const char* delim);
​
struct hostent* gethostbyname(const char* name);

为什么会出现这类函数呢?是因为最初编写很多 CRT 函数时,还没有多线程技术,所以很多函数内部实现都使用了函数内部的静态变量和全局变量。随着多线程技术的出现,很多函数出现了对应的多线程安全版本,如 localtime_r、strtok_r。在这些函数内部很多改用了线程局部存储 技术来替代原来使用静态变量或者全局变量的做法。

更多的专题参见:cppguide.cn

C/C++岗位内推、技术交流与求职群:加微信 cppxiaofang,备注加微信群。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值