欢迎大家来到IT世界,在知识的湖畔探索吧!
一、背景介绍
在的文章中,我们已经讲了线程安全函数与可重入函数的定义、区分和需要注意的地方,这次就针对C库常见的线程安全函数和非线程安全函数列表进行简单介绍,便于快速了解,并在后续的开发过程中尽可能使用线程安全函数,避免使用非线程安全函数。
二、C库常见线程安全函数列表
序号 |
函数 |
说明 |
1 |
calloc(); free(); malloc(); realloc() |
如果实现了 _mutex_* 函数,则堆函数是线程安全的。 在所有线程之间共享单个堆,并使用互斥量以避免进行并发访问时发生数据损坏。 每个堆实现都负责进行自己的锁定。 如果您提供了自己的分配器,它也必须进行自己的锁定。 这样,它便可进行精细锁定(如果需要),而不是简单地使用单个互斥量保护整个堆(粗放锁定)。 |
2 |
__alloca(); __alloca_finish(); __alloca_init(); __alloca_initialize() |
如果实现了 _mutex_* 函数,则 alloca 函数是线程安全的。每个线程的 alloca 状态包含在 __user_perthread_libspace 块中。这意味着多个线程不会发生冲突。 Note:请注意,alloca 函数也使用堆。 不过堆函数都是线程安全的。 |
3 |
abort(); raise(); signal(); fenv.h |
ARM 信号处理函数和 FP 异常捕获是线程安全的。 信号处理程序和 FP 捕获设置是整个进程中的全局设置,并使用锁对其进行保护。这样,即使多个线程同时调用 signal() 或 fenv.h 函数,也不会损坏数据。 但要注意,调用影响所有线程,而不是只影响调用线程。 |
4 |
clearerr();fclose();feof(); ferror();fflush();fgetc(); fgetpos();fgets();fopen(); fputc();fputs();fread(); freopen();fseek();fsetpos(); ftell();fwrite(); getc();getchar();gets(); perror();putc();putchar(); puts();rewind(); setbuf();setvbuf(); tmpfile();tmpnam(); ungetc() |
如果实现了 _mutex_* 函数,则 stdio 库是线程安全的。 每个单独的流都使用锁进行保护,因此,两个线程可以分别打开并使用其自己的 stdio 流,而不会相互干扰。 如果两个线程都要读取或写入相同的流,fgetc() 和 fputc() 级别的锁定可防止发生数据损坏,但是,每个线程的单独字符输出可能会交叉出现,因而容易造成混淆。 Note:请注意,tmpnam() 也包含一个静态缓冲区,但仅在自变量为NULL 时才使用它。要确保tmpnam() 使用是线程安全的,应提供您自己的缓冲区空间。 |
5 |
fprintf();printf(); vfprintf();vprintf(); fscanf(); scanf() |
使用这些函数时: 标准 C printf() 和 scanf() 函数使用 stdio,因而是线程安全的。 如果在多线程程序中调用标准 C printf(),其语言环境可能会发生变化。 |
6 |
clock() |
clock() 包含程序静态数据,此数据是在启动时一次性写入的,以后只能对其进行读取。 因此,clock() 是线程安全的,但前提是在初始化库时没有运行任何其他线程。 |
7 |
errno() |
errno 是线程安全的。 每个线程将其自己的 errno 存储在 __user_perthread_libspace 块中。 这意味着,每个线程可以单独调用 errno 设置函数,然后检查 errno,而不用担心受其他线程的影响。 |
8 |
atexit() |
atexit() 维护的退出函数列表是进程全局性的,并且使用锁对其进行保护。 在最坏的情况下,如果多个线程调用 atexit(),则不能保证调用退出函数的顺序。 |
9 |
abs();acos();asin();atan(); atan2();atof();atol();atoi(); bsearch();ceil();cos();cosh(); difftime();div();exp();fabs(); floor();fmod();frexp();labs(); ldexp();ldiv();log();log10(); memchr();memcmp(); memcpy();memmove(); memset(); mktime();modf();pow();qsort(); sin();sinh();sqrt();strcat(); strchr();strcmp();strcpy(); strcspn(); strlcat();strlcpy();strlen(); strncat();strncmp();strncpy(); strpbrk();strrchr();strspn(); strstr();strxfrm();tan();tanh() |
这些函数在本质上就是线程安全的。 |
10 |
longjmp(); setjmp() |
虽 setjmp() 和 longjmp() 在 __user_libspace 中保存数据,但它们均调用线程安全的__alloca_* 函数。 |
11 |
remove(); rename(); time() |
这些函数使用中断,以便与 ARM 调试环境进行通信。 通常,必须为实际应用程序重新实现这些函数。 |
12 |
snprintf();sprintf(); vsnprintf();vsprintf();sscanf(); isalnum();isalpha();iscntrl(); isdigit();isgraph();islower(); isprint();ispunct();isspace(); isupper();isxdigit();tolower(); toupper();strcoll();strtod(); strtol();strtoul();strftime() |
使用这些函数时,这些基于字符串的函数将读取语言环境。 通常,它们是线程安全的。 但是,如果在会话中更改语言环境,则必须确保这些函数不受影响。 基于字符串的函数并不依赖于 stdio 库,示例,sprintf() 和 sscanf()。 |
13 |
stdin;stdout;stderr |
这些函数是线程安全的。 |
三、C库常见非线程安全函数列表
序号 |
函数 |
说明 |
1 |
setlocale() |
语言环境设置是所有线程的全局设置,并且未使用锁对其进行保护。 如果两个线程调用 setlocale(),则可能会发生数据损坏。 另外,很多其他函数读取当前语言环境设置,示例,strtod() 和 sprintf()。 因此,如果一个线程调用 setlocale(),另一个线程同时调用此函数,则可能会产生意外结果。 ARM 建议您选择所需的语言环境,然后调用一次 setlocale() 以对其进行初始化。 应在程序中创建任何其他线程之前执行此操作,以使任意数量的线程可以同时读取语言环境设置,而不会相互干扰。 请注意,localeconv() 不是线程安全的。 应改用指向用户提供的缓冲区的指针调用 ARM 函数 _get_lconv()。 |
2 |
asctime(); localtime(); strtok() |
这些函数不是线程安全的。 每个函数都包含一个静态缓冲区,其他线程可能会在调用函数以及随后使用其返回值之间覆盖该缓冲区。 ARM 提供了可重入版本 _asctime_r()、_localtime_r() 和 _strtok_r()。 ARM 建议您改用这些函数以确保安全。 Note:这些可重入版本使用一些附加参数。_asctime_r() 使用的附加参数是指向输出字符串要写入的缓冲区的指针。_localtime_r() 使用的附加参数是指向结果要写入的 struct tm 的指针。_strtok_r() 使用的附加参数也是一个指针,指向的是指向下一个标记的 char 指针。 |
3 |
gamma()[1]; lgamma() |
这些扩展 mathlib 函数使用全局变量 _signgam,因此不是线程安全的。 |
4 |
mbrlen(); mbsrtowcs(); mbrtowc(); wcrtomb(); wcsrtombs() |
stdlib.h 中定义的 C89 多字节转换函数(如 mblen() 和 mbtowc())不是线程安全的,因为它们包含在所有线程之间共享而没有锁定的内部静态状态。 但是,wchar.h 中定义的扩展可重启版本(示例,mbrtowc() 和 wcrtomb())是线程安全的,但前提是您传入指向您自己的 mbstate_t 对象的指针。 如果要在处理多字节字符串时确保线程安全,这些函数只能使用非 NULL 的 mbstate_t * 参数。 |
5 |
exit() |
即使提供了基本 _sys_exit()(实际终止所有线程)的实现,也不要在多线程程序中调用 exit()。 在这种情况下,exit() 在调用 _sys_exit() 之前 先执行清除操作,因此会中断其他线程。 |
6 |
rand(); srand() |
这些函数保留全局性且不受保护的内部状态。 这意味着,rand() 调用从来都不是线程安全的。 ARM 建议您使用自己的锁定,以确保每次只有一个线程调用 rand(),示例,通过定义 $Sub$rand()(如果要避免更改代码)。 或者,也可以执行以下操作之一: 提供您自己的随机数生成器,它可能具有多个独立实例 硬性规定只有一个线程需要生成随机数。 |
四、总结
上面列出了C库常见线程安全函数与非线程安全函数列表,在不清楚的时候,可以随手翻出来看看,避免程序调用了非线程安全函数而导致在部分场景下异常,这往往需要花费较大的人力和时间,属于得不偿失的行为。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/90155.html