这几天调bug,该bug的特点是随机出现,而且不可重现!找了一天之后,终于发现,是因为在多线程函数中用了线程不安全的strtok函数导致的。
char *strtok(char *s, const char *delim); |
- 功能:分解字符串为一组字符串。s为要分解的字符串,delim为分隔符字符串。
- 说明:strtok()用来将字符串分割成一个个片段。参数s指向欲分割的字符串,参数delim则为分割字符串,当strtok()在参数s的字符串中发现到参数delim的分割字符时则会将该字符改为\0 字符。在第一次调用时,strtok()必需给予参数s字符串,往后的调用则将参数s设置成NULL。每次调用成功则返回被分割出片段的指针。
- 返回值:从s开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。所有delim中包含的字符都会被滤掉,并将被滤掉的地方设为一处分割的节点。
多线程不安全函数分类:
- 不保护共享产量的函数。
- 保持跨越多个调用状态的函数。
- 返回指向静态变量的指针的函数。
- 调用上面3类函数的函数,即调用线程不安全函数的函数。
多线程不安全函数原理简介
一个进程中有很多全局变量以及函数(error、strtok、asctime等),各个线程对这些变量会产生干扰。在多线程运行期库中每个线程有自己的本地存储空间,有时也会使用全局变量和静态变量,如果多线程随意同时访问全局变量和静态变量,就将出现意向不到的错误,这个在现在的多线程编程中一般都会通过加锁等机制解决,但我们往往忽略了C run-time library里面的一些函数使用了全局变量和静态变量却没用相应机制避免冲突。
对于strtok函数具体出问题原理是:
应用函数初次调用strtok时传递一个字串的地址,比如”aaa.bbb.dddd”,并将字串的地址保存在自己的静态变量中,当你将来再次调用strtok并传递NULL时(strtok的特殊用法,第二次调用时字串传NULL表示对第一次传进去的字串继续分隔,所以要先保存字串地址,这是有点怪异的实现),该函数就会引用保存好的字串地址。在多线程环境下,另一个线程也可能调用strtok,在这种环境下,另一个线程会在第一个线程不知道的情况下替换静态变量中的字串地址,这就会导致各种难以排除的错误出现。
其他线程不安全函数原理类似,解决办法是只要给这些全局变量或静态变量加锁就行了,不过我们一般都没机会改这些库,所以使用时就要小心。
多线程不安全的函数列表
多线程不安全的函数列表可以参考:
http://kernel.org/doc/man-pages/online/pages/man7/pthreads.7.html
asctime() |
basename() |
catgets() |
crypt() |
ctermid()//if passed a non-NULL argument |
ctime() |
dbm_clearerr() |
dbm_close() |
dbm_delete() |
dbm_error() |
dbm_fetch() |
dbm_firstkey() |
dbm_nextkey() |
dbm_open() |
dbm_store() |
dirname() |
dlerror() |
drand48() |
ecvt()//[POSIX.1-2001 only (function removed in POSIX.1-2008)] |
encrypt() |
endgrent() |
endpwent() |
endutxent() |
fcvt()//[POSIX.1-2001 only (function removed in POSIX.1-2008)] |
ftw() |
gcvt()//[POSIX.1-2001 only (function removed in POSIX.1-2008)] |
getc_unlocked() |
getchar_unlocked() |
getdate() |
getenv() |
getgrent() |
getgrgid() |
getgrnam() |
gethostbyaddr()//[POSIX.1-2001 only (function removed in POSIX.1-2008)] |
gethostbyname()//[POSIX.1-2001 only (function removed in POSIX.1-2008)] |
gethostent() |
getlogin() |
getnetbyaddr() |
getnetbyname() |
getnetent() |
getopt() |
getprotobyname() |
getprotobynumber() |
getprotoent() |
getpwent() |
getpwnam() |
getpwuid() |
getservbyname() |
getservbyport() |
getservent() |
getutxent() |
getutxid() |
getutxline() |
gmtime() |
hcreate() |
hdestroy() |
hsearch() |
inet_ntoa() |
l64a() |
lgamma() |
lgammaf() |
lgammal() |
localeconv() |
localtime() |
lrand48() |
mrand48() |
nftw() |
nl_langinfo() |
ptsname() |
putc_unlocked() |
putchar_unlocked() |
putenv() |
pututxline() |
rand() |
readdir() |
setenv() |
setgrent() |
setkey() |
setpwent() |
setutxent() |
strerror() |
strsignal()//[Added in POSIX.1-2008] |
strtok() |
system()//[Added in POSIX.1-2008] |
tmpnam()//if passed a non-NULL argument |
ttyname() |
unsetenv() |
wcrtomb()//if its final argument is NULL |
wcsrtombs()//if its final argument is NULL |
wcstombs() |
wctomb() |
很受益,竟然有这么多非线程安全的底层函数