[C/C++线程安全]_[中级]_[避免使用的C线程不安全函数]
场景
- 在开发
C/C++
时,多线程执行任务算是很常见的了。但是C98
还遗留了一些C
函数并不支持多线程调用,如果调用会出现未定义行为。那么这些函数是哪些?有什么替代方法吗?
说明
- 目前线程不安全的
C
标准库函数有rand,strtok,ctime,asctime,localtime,gmtime
等。strtok,ctime,asctime,localtime,gmtime
在C11
标准里已经有安全的实现,都是带_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);
- 而
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);
}
- 时间函数
ctime,asctime,localtime,gmtime
都用到方法内的静态存储区。
例子
- 这些函数都不是线程安全的了,建议尽快替换,以避免产生未定义行为。
// 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
参考
-
rand
-
Pseudo-random number generation
-
std::uniform_int_distribution
-
std::random_device
-
std::mt19937 std::mersenne_twister_engine
-
std::minstd_rand std::linear_congruential_engine
-
ctime, ctime_s
-
asctime, asctime_s
-
strtok, strtok_s
-
C++11语言特性和标准库-第一部