2.1 线程安全
“线程安全”由多线程对共享资源的访问引起。呵呵,前面讲了共享为线程带来的便利,现在,共享又引发了程序的安全问题了,真是利弊互现,不可偏颇啊。
那么,什么是线程安全呢?
一般而言,如果调用某个接口时需要我们自己采取同步措施来保护该接口访问的共享资源,则这样的接口不是线程安全的。
如果接口中访问的数据都属于私有数据,那么这样的接口是线程安全的,或者几个接口对共享数据都是只读操作,那么这样的接口也是线程安全的。
如果多个接口之间有共享数据,而且有读有写的话,如果设计者自己采取了同步措施,调用者不需要考虑数据同步问题,则这样的接口是线程安全的,否则不是线程安全的。
举个例子说,在Linux系统下,有个著名的函数“getopt”(还有个“getopt_long”),其作用是用来解析命令行参数的标准函数,但是,如果我们在多线程的代码中使用了这两个函数,则会带来线程不安全的问题。为什么?因为getopt使用了全局变量。
如下代码摘自PG的pgsql/src/port/getopt.c[1],我们对其做一分析:
1. 被标识为粗体带有颜色的变量,是全局变量,被Linux系统的“getopt.h”文件定义。
2. 假设一个进程启动了TA和TB两个线程,这两个线程都调用了getopt函数,不管是TA还是TB执行,它们对optind的初值要求均是0;现在,TA先执行,TB后,那么,在TB执行时,optind的值是多少呢?很显然,在TA执行后,optind的值不再是初值“0”,作为全局变量,其值被保存到TB执行之时,但这显然不能满足TB对optind初值的要求,这就引发了线程安全的问题。
3. 注意PG在getopt函数的使用上没有线程安全的问题(PG在调用getopt时都是单独的进程,不存在多线程的问题)。
4. 与getopt函数还有很多类似的系统函数,如strtok, asctime, ctime, gmtime, localtime,rand等函数,都不是线程安全的,在多线程编程中使用时,务必小心。
……
int optopt; /* character checked for validity */
……
/*
* getopt
* Parse argc/argv argument vector.
*/
int
getopt(nargc, nargv, ostr)
int nargc;
char *const * nargv;
const char *ostr;
{
static char *place = EMSG; /* option letter processing */
char *oli; /* option letter list index */
if (optreset || !*place)
{ /* update scanning pointer */
optreset = 0;
if (optind >= nargc || *(place = nargv[optind]) != '-')
{
place = EMSG;
return -1;
}
if (place[1] && *++place == '-' && place[1] == '\0')
{ /* found "--" */
++optind; //全局变量的值发生改变
place = EMSG;
return -1;
}
} /* option letter okay? */
if ((optopt = (int) *place++) == (int) ':' ||
!(oli = strchr(ostr, optopt)))
{
/*
* if the user didn't specify '-' as an option, assume it means -1.
*/
if (optopt == (int) '-')
return -1;
if (!*place)
++optind; //全局变量的值发生改变
if (opterr && *ostr != ':')
(void) fprintf(stderr,
"illegal option -- %c\n", optopt);
return BADCH;
}
if (*++oli != ':')
{ /* don't need argument */
optarg = NULL;
if (!*place)
++optind; //全局变量的值发生改变
}
else
{ /* need an argument */
if (*place) /* no white space */
optarg = place;
else if (nargc <= ++optind) //全局变量的值发生改变
{ /* no arg */
place = EMSG;
if (*ostr == ':')
return BADARG;
if (opterr)
(void) fprintf(stderr,
"option requires an argument -- %c\n",
optopt);
return BADCH;
}
else
/* white space */
optarg = nargv[optind];
place = EMSG;
+