分类

可重入的函数

2017-10-29 22:36 programming

可重入的函数有这样的特性: 当该函数被调用时, 不会影响之前的 状态. 它们是线程安全的, 而且也可以在信号处理函数(signal handler) 里面使用. 它们没有副作用.

举例来说, read(), write() 都是可重入的, 因为它们的状态, 比如 缓存, 文件偏移量都是在内核中维护的. 而 fread(), fwrite(), printf(), 这些都不是可重入的. 它们的缓存是由 libc 维护的, 极端的 情况下, libc 在处理缓存时被内核中断了, 触发了信号处理函数, 在信号 处理函数内部, 调用 printf() 时, libc 之前的缓存状态就被修改了.

另外的例子, gethostbyname() 也不是可重入的, 因为它内部声明了静态对象. 比如:

static struct hostent host;

当有网络服务器多个线程调用它时, 这个静态对象的值就会被重写, 全局的.

解决方法是, 可以把 gethostbyname() 换成 gethostbyname_r() , 由调用者负责分配结构体. 但是后者要额外增加几个参数: hostent 结构体, 用来存放其它信息的缓存区域, 缓存 区的大小, 甚至还要有一个指向用于存放错误状态的指针. 但是, 这种方法也有问题, 调用者究竟要分存多大的缓存区域? 文档里面只说了

"The buffer must be large enough to hold all of the data associated
with the host entry."

查看它的源码, 内部使用了 8192 字节来保存别名及地址.

或者像 chromium 那样做的, Browser 进程里面专门有一个线程来处理 DNS 请求及缓存.

类似的问题, 更干净的方法是像 getaddrinfo(), 它不分配静态变量, 而是每次 调用时都新分配一块内存 (malloc()), 然后调用者要负责释放这块内存, 通过 freeaddrinfo() 函数. 但是问题在于, 当调用者忘记释放这块内存时, 就发生了内存 泄露.