2.2.6. ARM C 库中的线程安全性

在 ARM 库中,函数可能是线程安全的,如下所示:

如果应用程序以隐藏方式使用 ARM 库(如使用语言辅助函数),则可能会出现线程问题。

线程安全的函数

Table 2.1 显示了线程安全的 C 库函数。

Table 2.1. 线程安全的函数 

函数 说明
calloc()free()malloc()realloc()

线程安全的堆函数。

在所有线程之间共享单个堆,并使用互斥锁以避免进行并发访问时发生数据损坏。 每个堆实现都负责进行自己的锁定。 如果您提供了自己的分配器,它也必须进行自己的锁定。 这样,它便可进行精细锁定(如果需要),而不是简单地使用单个互斥锁保护整个堆(粗放锁定)。

__alloca()、__alloca_finish()__alloca_init()__alloca_initialize()

alloca 函数是线程安全的。

每个线程的 alloca 状态包含在 __user_perthread_libspace 块中。 这意味着,多个线程不会发生冲突。

Note

请注意,alloca 函数也使用堆。 不过,堆函数都是线程安全的。

abort()raise()signal()、

fenv.h

ARM 信号处理函数和 FP 异常捕获是线程安全的。

信号处理程序和 FP 捕获设置是整个进程中的全局设置,并使用锁对其进行保护。 这样,即使多个线程同时调用 signal()fenv.h 函数,也不会损坏数据。 但要注意,调用影响所有线程,而不是只影响调用线程。

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()

stdio 库是线程安全的。

每个单独的流都使用锁进行保护,因此,两个线程可以分别打开并使用其自己的 stdio 流,而不会相互干扰。

如果两个线程都要读取或写入相同的流,fgetc()fputc() 级别的锁定可防止发生数据损坏,但是,每个线程的单独字符输出可能会交叉出现,因而容易造成混淆。

Note

请注意,tmpnam() 也包含一个静态缓冲区,但仅在自变量为 NULL 时才使用它。 要确保 tmpnam() 使用是线程安全的,应提供您自己的缓冲区空间。

fprintf()printf()vfprintf()vprintf()fscanf()scanf()

使用这些函数时:

  • 标准 C printf()scanf() 函数使用 stdio,因而是线程安全的。

  • 如果在多线程程序中调用标准 C printf(),其区域设置设置可能会发生变化。

                    clock()
                  
clock() 包含程序静态数据,此数据是在启动时一次性写入的,以后只能对其进行读取。 因此,clock() 是线程安全的,但前提是在初始化库时没有运行任何其他线程。
                    errno()
                  

errno 是线程安全的。

每个线程将其自己的 errno 存储在 __user_perthread_libspace 块中。 这意味着,每个线程可以单独调用 errno 设置函数,然后检查 errno,而不用担心受其他线程的影响。

                    atexit()
                  

atexit() 维护的退出函数列表是进程全局性的,并且使用锁对其进行保护。

在最坏的情况下,如果多个线程调用 atexit(),则不能保证调用退出函数的顺序。

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()

这些函数是线程安全的。

longjmp()setjmp()

虽然 setjmp()longjmp()__user_libspace 中保存数据,但它们均调用线程安全的 __alloca_* 函数。

remove()rename()time()

这些函数使用中断,以便与 ARM 调试环境进行通信。 通常,必须为实际应用程序重新实现这些函数。

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()

stdinstdoutstderr
这些函数是线程安全的。
FP 状态字

可以在多线程环境(甚至软件浮点)中安全地使用 FP 状态字。 其中,每个线程的状态字存储在其自己的 __user_perthread_libspace 块中。

Note

请注意,在硬件浮点中,FP 状态字存储在 VFP 寄存器中。 在这种情况下,线程切换机制必须为每个线程保留该寄存器的单独副本。

非线程安全的函数

Table 2.2 显示了非线程安全的 C 库函数。

Table 2.2. 非线程安全的函数 

函数 说明
                    setlocale()
                  

区域设置设置是所有线程的全局设置,并且未使用锁对其进行保护。 如果两个线程调用 setlocale(),则可能会发生数据损坏。 另外,很多其他函数读取当前区域设置设置,例如,strtod()sprintf()。 因此,如果一个线程调用 setlocale(),另一个线程同时调用此函数,则可能会产生意外结果。

ARM 建议您选择所需的区域设置,然后调用一次 setlocale() 以对其进行初始化。 应在程序中创建任何其他线程之前执行此操作,以使任意数量的线程可以同时读取区域设置设置,而不会相互干扰。

请注意,localeconv() 不是线程安全的。 应改用指向用户提供的缓冲区的指针调用 ARM 函数 _get_lconv()

asctime()localtime()strtok()

这些函数不是线程安全的。 每个函数都包含一个静态缓冲区,其他线程可能会在调用函数以及随后使用其返回值之间覆盖该缓冲区。

ARM 提供了可重入版本 asctime_r()localtime_r()strtok_r()(由 POSIX 指定)。 ARM 建议您改用这些函数以确保安全。

Note

这些可重入版本使用额外一些参数,它们是指向下一个标记的 char 指针的指针。

gamma()lgamma()
这些扩展 mathlib 函数(在 math.h 中定义)使用全局变量 signgam,因此不是线程安全的。 ARM 建议您改用 gamma_r()lgamma_r()。 这些函数将结果符号写入到提供的 int * 自变量所指向的内存位置。
mbrlen()mbsrtowcs() mbrtowc()wcrtomb()wcsrtombs()

stdlib.h 中定义的 C89 多字节转换函数(如 mblen()mbtowc())不是线程安全的,因为它们包含在所有线程之间共享而没有锁定的内部静态状态。

但是,wchar.h 中定义的扩展可重启版本(mbrtowc()wcrtomb())是线程安全的,但前提是传入指向您自己的 mbstate_t 对象的指针。 如果要在处理多字节字符串时确保线程安全,这些函数只能使用非 NULL 的 mbstate_t * 参数。

                    exit()
                  

即使提供了基本 _sys_exit()(实际终止所有线程)的实现,也不要在多线程程序中调用 exit()

在这种情况下,exit() 在调用 _sys_exit()之前 先执行清除操作,因此中断其他线程。

rand()srand()

这些函数保留全局性且不受保护的内部状态。 这意味着,rand() 调用从来都不是线程安全的。

ARM 建议您使用自己的锁定,以确保每次只有一个线程调用 rand(),例如,通过定义 $Sub$$rand()(如果要避免更改代码)。

或者,也可以执行以下操作之一:

  • 提供您自己的随机数生成器,它可能具有多个独立实例

  • 硬性规定只有一个线程需要生成随机数。

Copyright © 2007 ARM Limited. All rights reserved. ARM DUI 0349AC
Non-Confidential