asctime(localtime)在gcc4.8.4正常gcc9.4.0多线程异常
问题原因说明
localtime和asctime都是C标准明确标注的非线程安全函数:两个函数内部都使用全局静态缓冲区存储返回结果,多线程并发调用时会出现缓冲区内容互相覆盖的问题,属于典型的未定义行为。- gcc 4.8.4环境下运行正常完全是巧合:旧版本配套的glibc中,这两个函数的静态缓冲区位置、程序编译后的内存布局恰好让并发覆盖操作没有破坏关键计算数据,属于未定义行为偶然表现为正常,不代表代码本身没有问题。
- 切换到gcc 9.4.0后出错的核心原因:新版gcc配套的glibc升级后,调整了这两个时间函数的内部实现和静态缓冲区位置,多线程无锁并发调用时的缓冲区踩踏恰好破坏了存储计算结果的
arr_DFT数组内存,直接导致最终写入文件的结果错误。 - 从代码逻辑看,线程函数在
pthread_mutex_unlock(&mutex)之后还有2次无锁的asctime(localtime(<ime))调用,这部分代码是多线程并发执行的,是触发内存踩踏的核心位置。
通过Makefile临时修复的方法
可以通过添加编译宏的方式临时规避问题,但该方法依赖glibc的非标准扩展,不保证跨环境兼容,仅适合临时验证使用。
你当前的Makefile内容如下:
all: gcc -Wall main.c -o hw5 -pthread -lm clean: rm -f *o && rm -f hw5
添加-D_REENTRANT -D_GNU_SOURCE编译参数后,部分版本的glibc会自动将非线程安全的localtime、asctime调用重定向到可重入版本,临时解决并发踩踏问题,修改后的编译规则:
all: gcc -Wall main.c -o hw5 -pthread -lm -D_REENTRANT -D_GNU_SOURCE clean: rm -f *o && rm -f hw5
标准根治方案
不要依赖编译参数的非标准行为,直接将所有非线程安全的时间函数替换为POSIX标准定义的可重入版本,从代码层面彻底解决问题:
- 用
localtime_r替换localtime:该函数要求调用者自行传入struct tm结构体的存储地址,不使用全局静态缓冲区 - 用
asctime_r替换asctime:该函数要求调用者自行传入字符串存储缓冲区,不使用全局静态缓冲区
替换示例(以线程函数中的日志打印代码为例):
原问题代码:
sprintf(printBuffer,"%sThread %d has arrived the rendezvous point in %.2f seconds.\n",asctime(localtime(<ime)),tempVal+1, time_spend);
修改为可重入版本:
struct tm tm_tmp; char time_buf[26]; // asctime固定输出26字节长度(含结尾空字符) localtime_r(<ime, &tm_tmp); asctime_r(&tm_tmp, time_buf); sprintf(printBuffer,"%sThread %d has arrived the rendezvous point in %.2f seconds.\n",time_buf,tempVal+1, time_spend);
代码中所有调用时间函数的位置(主线程1处、线程函数3处)都需要按上述逻辑替换,替换后无需额外编译参数即可在所有POSIX兼容环境下保证线程安全,不会出现内存踩踏问题。
内容的提问来源于stack exchange,提问作者cBwavez
相关产品推荐
相关产品推荐

