当前位置: 首页 > news >正文

[C/C++线程安全]_[中级]_[避免使用的C线程不安全函数]

场景

  1. 在开发C/C++时,多线程执行任务算是很常见的了。但是C98还遗留了一些C函数并不支持多线程调用,如果调用会出现未定义行为。那么这些函数是哪些?有什么替代方法吗?

说明

  1. 目前线程不安全的C标准库函数有rand,strtok,ctime,asctime,localtime,gmtime等。strtok,ctime,asctime,localtime,gmtimeC11标准里已经有安全的实现,都是带_s结尾的同名函数。strtok_s,ctime_s,asctime_s,localtime_s,gmtime_s,主要就是删除了方法内部的静态存储区,放到了函数外部作为局部变量传入。比如strtok_s的多了一个context,记录了分割的状态。
char kStr[] = "1;2;3;4;5;";
char* context = NULL;
auto delimiter = ";";
auto token = strtok_s(kStr, delimiter, &context);
  1. rand没有对应的rand_s版本。rand依赖一个全局变量,之后这个变量在多线程调用时会发生数据竞争,产生未定义行为。可以使用C++11提供的<random>库。这个库提供了多种随机数生成引擎,特别是std::mt19937是真随机数,很难预测,如果对随机数的要求不高,可以用高性能的minstd_rand引擎。 特别要注意,随机数引擎共享实例不是线程安全的,所以需要一个thread_local来存储,不同线程有不同的实例。
int toRandInt(int min,int max) 
{//thread_local std::mt19937 gen(std::random_device{}());thread_local std::minstd_rand gen(std::random_device{}());std::uniform_int_distribution<> dist(min, max);return dist(gen);
}
  1. 时间函数ctime,asctime,localtime,gmtime都用到方法内的静态存储区。

例子

  1. 这些函数都不是线程安全的了,建议尽快替换,以避免产生未定义行为。
// test-thread-safe-cfunc.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include <string>
#include <sstream>
#include <random>
#include <vector>
#include <thread>
#include <mutex>
#include <string.h>using namespace std;static mutex rMutex;
static stringstream rData;void TestStrtokS()
{cout << "============= TestStrtokS ===========" << endl;// -- 需要可修改的字符数组char kStr[] = "1;2;3;4;5;";char* context = NULL;auto delimiter = ";";auto token = strtok_s(kStr, delimiter, &context);while (token) {cout << "token: " << token << endl;token = strtok_s(NULL, delimiter, &context);}}int toRandInt(int min,int max) 
{// `thread_local` 修饰符的变量只初始化一次,每个线程一个`gen`,避免多线程调用同一个`gen`导致数据竞争。// 使用`minstd_rand`生成性能高,简单的可预测的随机数。// 使用`mt19937` 生成性能稍低,很难预测的随机数。//thread_local std::mt19937 gen(std::random_device{}());thread_local std::minstd_rand gen(std::random_device{}());std::uniform_int_distribution<> dist(min, max);return dist(gen);
}void TestCtime()
{cout << "============= TestCtime ===========" << endl;// `ctime`它返回一个指向静态缓冲区的指针,该缓冲区内容可能会被后续调用覆盖。// -- 非线程安全// `C11`的`ctime_s`线程安全。// `ctime_s`字符串长度固定为`26`字节char buf[32] = {0};time_t now;time(&now);if (ctime_s(buf, sizeof(buf), &now) == 0)cout << buf << endl;}void TestAsctimeLocaltime()
{cout << "============= TestAsctime ===========" << endl;// `asctime`它返回一个指向静态缓冲区的指针,该缓冲区内容可能会被后续调用覆盖。// -- 非线程安全// `C11`的`asctime_s`线程安全。// `localtime_s` 线程安全的,需要一个外部传入的`tm`指针存储输出数据。// `asctime_s`字符串长度固定为`26`字节time_t now;struct tm tm_info;char time_buf[26] = {0};time(&now);                        // 获取当前时间(time_t)localtime_s(&tm_info, &now);       // 转为本地时间的 tm 结构体(安全版 localtime)if (asctime_s(time_buf, sizeof(time_buf), &tm_info) == 0) {cout << time_buf << endl;}struct tm tm_buf;gmtime_s(&tm_buf, &now);cout << tm_buf.tm_year + 1900 << "-" << tm_buf.tm_mon + 1 << "-" << tm_buf.tm_mday << endl;
}void doRand(int index) {string str;for (int i = 0; i < 20; i++) {std::unique_lock<mutex> myLock(rMutex);str.append(to_string(toRandInt(1, 100))).append(";");}std::unique_lock<mutex> myLock(rMutex);rData << "index: " << index << " str: " << str  << "\n";
}void TestRand()
{cout << "============= TestRand ===========" << endl;auto kSize = 10;vector<thread> ts;ts.reserve(kSize);for (int i = 0; i < kSize; i++) {ts.emplace_back(doRand,i);}for (int i = 0; i < kSize; i++) {ts.at(i).join();}auto str = rData.str();cout << str << endl;
}int main()
{std::cout << "Hello World!\n";TestStrtokS();TestRand();TestCtime();TestAsctimeLocaltime();
}

输出

Hello World!
============= TestStrtokS ===========
token: 1
token: 2
token: 3
token: 4
token: 5
============= TestRand ===========
index: 0 str: 34;79;14;92;47;73;95;93;58;39;47;99;81;41;52;86;5;3;74;94;
index: 2 str: 86;96;49;49;64;87;77;27;12;34;79;66;19;81;73;88;99;51;27;35;
index: 6 str: 9;30;85;78;20;73;61;8;22;89;80;87;49;46;54;88;52;36;79;22;
index: 1 str: 26;70;18;36;65;2;78;85;2;58;57;8;31;73;63;26;47;41;63;46;
index: 4 str: 63;29;45;72;32;90;32;50;73;97;31;91;66;75;40;13;33;35;6;69;
index: 5 str: 96;80;33;15;59;19;87;72;98;88;67;96;46;95;59;67;16;19;15;71;
index: 7 str: 25;25;98;60;49;51;42;59;71;29;36;23;97;15;36;60;2;60;18;85;
index: 8 str: 6;80;43;31;87;67;69;84;92;75;92;9;73;44;32;82;80;15;50;69;
index: 3 str: 13;94;56;98;91;49;96;17;11;93;7;81;8;87;38;79;100;11;7;19;
index: 9 str: 35;76;47;64;54;19;50;67;77;49;71;88;77;26;65;16;10;23;98;6;============= TestCtime ===========
Sun Aug 10 11:18:43 2025============= TestAsctime ===========
Sun Aug 10 11:18:43 20252025-8-10

参考

  1. rand

  2. Pseudo-random number generation

  3. std::uniform_int_distribution

  4. std::random_device

  5. std::mt19937 std::mersenne_twister_engine

  6. std::minstd_rand std::linear_congruential_engine

  7. ctime, ctime_s

  8. asctime, asctime_s

  9. strtok, strtok_s

  10. C++11语言特性和标准库-第一部

http://www.dtcms.com/a/324497.html

相关文章:

  • Android APK 使用OpenGl 绘制三角形源码
  • Word XML 批注范围克隆处理器
  • 绕过文件上传漏洞并利用文件包含漏洞获取系统信息的技术分析
  • 使用MongoDB存储和计算距离
  • Spring Boot 2 升级 Spring Boot 3 的全方位深度指南
  • Leetcode 3644. Maximum K to Sort a Permutation
  • 9_基于深度学习的车型检测识别系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • Error: error:0308010C:digital envelope routines::unsupported at new Hash
  • 【Python 小脚本·大用途 · 第 3 篇】
  • 编译xformers
  • 【深度学习新浪潮】遥感图像风格化迁移研究工作介绍
  • 学习记录(十九)-Overleaf如何插入图片(单,多)
  • 学习模板元编程(3)enable_if
  • CART算法:Gini指数
  • 25.机器学习入门:让机器变聪明的魔法课
  • 串口通信初始化过程是怎样的???
  • IDEA 快捷编辑指南
  • Java开源代码源码研究:我的成长之路与实战心得分享
  • IDEA 安装插件的两种方式
  • 【面试场景题】异地多活改造方案
  • AI大模型--提示词工程
  • CVPR医学图像三套创新方案:通用分割+3D高效解码+SSM肿瘤定位(附链接)
  • 如何解决网站长期不连接数据库后首次连接缓慢的问题?
  • JS--判断是对象还是数组
  • Spring之【详解AOP】
  • 使用 Docker-Compose 部署 Redis 三主三从集群(含 Exporter 监控)
  • SQL Server从入门到项目实践(超值版)读书笔记 23
  • Windows 11 安装 JDK 11
  • ThreadLocal的原理是什么,使用场景有哪些?
  • 【自动化运维神器Ansible】playbook案例解析:Handlers与Notify机制深度解析