11.3.2 CRT改进
使用TLS
多线程运行库具有什么样的改进呢?首先,errno必须成为各个线程的私有成员。在glibc中,errno被定义为一个宏,如下:
#define errno (*__errno_location ()) |
函数__errno_location在不同的库版本下有不同的定义,在单线程版本中,它仅直接返回了全局变量errno的地址。而在多线程版本中,不同线程调用__errno_location返回的地址则各不相同。在MSVC中,errno同样是一个宏,其实现方式和glibc类似。
加锁
在多线程版本的运行库中,线程不安全的函数内部都会自动地进行加锁,包括malloc、printf等,而异常处理的错误也早早就解决了。因此使用多线程版本的运行库时,即使在malloc/new前后不进行加锁,也不会出现并发冲突。
改进函数调用方式
C语言的运行库为了支持多线程特性,必须做出一些改进。一种改进的办法就是修改所有的线程不安全的函数的参数列表,改成某种线程安全的版本。比如MSVC的CRT就提供了线程安全版本的strtok()函数:strtok_s,它们的原型如下:
char *strtok(char *strToken, const char *strDelimit ); char *strtok_s( char *strToken, const char *strDelimit, char **context); |
改进后的strtok_s增加了一个参数,这个参数context是由调用者提供一个char*指针,strtok_s将每次调用后的字符串位置保存在这个指针中。而之前版本的strtok函数会将这个位置保存在一个函数内部的静态局部变量中,如果有多个线程同时调用这个函数,有可能出现冲突。与MSVC CRT类似,Glibc也提供了一个线程安全版本的strtok()叫做strtok_r()。
但是很多时候改变标准库函数的做法是不可行的。标准库之所以称之为"标准",就是它具有一定的权威性和稳定性,不能随意更改。如果随意更改,那么所有遵循该标准的程序都需要重新进行修改,这个"标准"是不是值得遵循就有待商榷了。所以更好的做法是不改变任何标准库函数的原型,只是对标准库的实现进行一些改进,使得它能够在多线程的环境下也能够顺利运行,做到向后兼容。
【责任编辑:
云霞 TEL:(010)68476606】