C errno是否是线程安全的
本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/138
在使用多线程时,遇到了一个问题:线程例程中如果需要使用errno全局变量,如何保证errno的线程安全性?例如一个简单的线程池代码:
for(int i=0;i<THREADNUM;i++){
pthread_create(&pid,NULL,start_routine,NULL);
}
while(1){
connfd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientlen);
sbuf_insert(&buf,connfd); //put connfd into pool
}
void *start_routine(void *argv){
int connfd;
int p = pthread_detach(pthread_self());
while(1){
connfd = sbuf_remove(&buf); //thread get connfd
doit(connfd); //what if doit set global variable errno
close(connfd);
}
return NULL;
}
关于C中错误处理的问题,可以参考Error Handling in C programs,简单的说很多系统调用只会返回成功或者失败,具体失败的原因会设置全局变量errno供调用方自己读取,所以引发了多线程里errno线程安全的问题。
如何解决这个问题?毕竟设置errno的过程我们不能干预。上网搜了才发现,在POSIX标准中,重定义了errno,使之为线程安全的变量:
Redefinition of errno
In POSIX.1, errno is defined as an external global variable. But this definition is unacceptable in a multithreaded environment, because its use can result in nondeterministic results. The problem is that two or more threads can encounter errors, all causing the same errno to be set. Under these circumstances, a thread might end up checking errno after it has already been updated by another thread.
To circumvent the resulting nondeterminism, POSIX.1c redefines errno as a service that can access the per-thread error number as follows (ISO/IEC 9945:1-1996, §2.4):
Some functions may provide the error number in a variable accessed through the symbol errno. The symbol errno is defined by including the header <errno.h>, as specified by the C Standard ... For each thread of a process, the value of errno shall not be affected by function calls or assignments to errno by other threads.
颠覆了我的世界观呀,那这怎么实现的全局变量能够线程安全呢?
在error.h中(vim下按gf跳到库文件),看到如下定义:
/* Declare the `errno' variable, unless it's defined as a macro by
bits/errno.h. This is the case in GNU, where it is a per-thread
variable. This redeclaration using the macro still works, but it
will be a function declaration without a prototype and may trigger
a -Wstrict-prototypes warning. */
#ifndef errno
extern int errno;
#endif
如果bits/error.h中没有定义errno,才会定义errno。
在bits/errno.h中,找到关于errno定义部分:
extern int *__errno_location (void) __THROW __attribute__ ((__const__));
# if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value. */
# define errno (*__errno_location ())
# endif
可以清晰的看到,bits/errno.h对errno进行了重定义。从 __attribute__ ((__const__))
推测出__errno_location ()
会返回与参数无关的与线程绑定的一个特定地址,应用层直接从该地址取出errno的。(关于__attribute__
用法可以参考Using GNU C attribute)。但是上面使用了条件编译,也就是有两种方法可以使得gcc重定义errno:
- 不定义宏_LIBC
- 定义宏_LIBC_REENTRANT
但是很有意思的是,我们在编译时,压根不能设置_LIBC,感兴趣的可以自己试一下:
gcc -D_LIBC a.c
In file included from /usr/include/gnu/stubs.h:9:0,
from /usr/include/features.h:385,
from /usr/include/stdio.h:28,
from a.c:1:
/usr/include/gnu/stubs-64.h:7:3: error: #error Applications may not define the macro _LIBC
#error Applications may not define the macro _LIBC
^
我们在编译时设置了宏_LIBC,但是编译失败,原来在gnu/stubs-64.h
中会检测,如果有_LIBC宏定义,直接报错终止预编译:
#ifdef _LIBC
#error Applications may not define the macro _LIBC
#endif
也就是在正常情况下,我们使用gcc编译的程序,全局变量errno一定是线程安全的。现在只剩下了一个问题,__errno_location
是怎么实现的。遗憾的是,我并没有找到这个函数的实现,我们可以写个小程序反汇编看一下在哪实现的:
#include <stdio.h>
#include <errno.h>
int main(){
int i = errno;
printf("%d",i);
return 0;
}
反汇编代码:
0x0000000000400588 in main ()
=> 0x0000000000400588 <main+8>: e8 7b fe ff ff callq 0x400408 <__errno_location@plt>
从@plt看出应该是动态库延迟绑定,进去看看:
0x0000003dc8e148c0 in _dl_runtime_resolve () from /lib64/ld-linux-x86-64.so.2
=> 0x0000003dc8e148c0 <_dl_runtime_resolve+0>: 48 83 ec 38 sub $0x38,%rsp
呃呃,难道__errno_location
定义在linux代码中?算了不看了,到这已经基本解决了我的问题:多线程如何保证errno全局变量的线程安全性,哈哈。猜测实现方式应该跟thread-local有关。
最后,虽然在多线程中我们不用保证errno的线程安全,但是如果需要编写信号处理函数时,我们仍然要保证errno的安全性,因为操作系统可能不会新创建一个线程来处理信号处理函数:
void handle_signal(int sig){
int savedErrno;
savedErrno = errno;
/* Do something when recevied this sig */
errno = savedErrno;
}
参考资料:
C errno是否是线程安全的的更多相关文章
- 《UNIX环境高级编程》笔记--errno是否是线程安全的?
当UNIX函数出错时,常常返回一个负数,而且整形变量errno通常被设置为含有附加信息的一个值,例如,open函数如成功,返回 一个非负文件描述符,如果出错就返回-1,在open出错时,有大约15种不 ...
- epoll函数与参数总结学习 & errno的线程安全
select/poll被监视的文件描述符数目非常大时要O(n)效率很低:epoll与旧的 select 和 poll 系统调用完成操作所需 O(n) 不同, epoll能在O(1)时间内完成操作,所以 ...
- PHP CLI编程基础知识积累(进程、子进程、线程)
.note-content { font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", STHeit ...
- (九) 一起学 Unix 环境高级编程 (APUE) 之 线程
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- Linux 进程与线程一(创建-关闭线程)
进程是一个实体.每一个进程都有他自己的内存地址段(heap,stack等等) 进程是执行中的程序. 程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体. 进程是操作系统中最基 ...
- Linux多线程编程——多线程与线程同步
多线程 使用多线程好处: 一.通过为每种事件类型的处理单独分配线程,可以简化处理异步事件的代码,线程处理事件可以采用同步编程模式,启闭异步编程模式简单 二.方便的通信和数据交换 由于进程之间具有独立的 ...
- Linux 线程与进程,以及通信
http://blog.chinaunix.net/uid-25324849-id-3110075.html 部分转自:http://blog.chinaunix.net/uid-20620288-i ...
- 多线程编程之Windows环境下创建新线程
转自: http://www.cnblogs.com/lgxqf/archive/2009/02/10/1387480.html 在 Win32 API 中,创建线程的基本函数是 CreateThre ...
- errno多线程安全(转载)
一.errno的由来 在C编程中,errno是个不可缺少的变量,特别是在网络编程中.如果你没有用过errno,那只能说明你的程序不够健壮.当然,如果你是WIN32平台的GetLastError ...
随机推荐
- Azure进阶攻略丨Azure网络通不通,PsPing&PaPing告诉你答案
很多时候,为了解决一些问题,要查各种文档,很麻烦你造吗!做「伸手党」又容易被鄙视,这时候就需要用到[Azure 进阶攻略]啦!特此,我们推出关于 Azure 常用操作指南的系列文章,每篇涉及一个 Az ...
- (一)svn介绍
项目管理中的版本控制问题 通常软件开发由多人协作开发,如果对代码文件.文档等没有进行版本控制,将会出现很多问题: 备份多个版本,占用磁盘空间大 解决代码冲突困难 容易引发BUG 难于恢复至以前正确版本 ...
- CRUD全栈式编程架构之数据层的设计
CodeFirst 一直以来我们写应用的时候首先都是创建数据库 终于在orm支持codefirst之后,我们可以先建模. 通过模型去创建数据库,并且基于codefirst可以实现方便的 实现数据库迁移 ...
- Android(java)学习笔记46:反射机制
1. 反射机制: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称 ...
- echarts图表与可视窗口的自适应
由于要适应屏幕尺寸,发现了这个问题.网上搜到了两个办法,如下: 方法一: window.onresize = mychart.resize; 方法二: window.addEventListener( ...
- c#主窗体以及副窗体弹出
在program.cs中,Form1的位置就是主窗体的位置(主窗体特征:关闭窗体应用程序结束) 弹出副窗口(点击按钮弹出窗口) Close为关闭窗口(关闭对应对象,需要先自己new一个) this.C ...
- 【洛谷P1159】排行榜
排行榜 题目链接 看到题解中一个很巧妙的做法: 先确定SAME的位置, 将DOWN的按输入顺序从上往下输出 再将UP的接着从上往下输出 这样便可以保证DOWN的人名次一定下降了 UP的人名次一定上升了 ...
- java GZIP 压缩数据
package com.cjonline.foundation.cpe.action; import java.io.ByteArrayInputStream; import java.io.Byte ...
- 剑指offer——27. 二叉搜索树与双向链表(Java版)
题目: 剑指offer的题目有挺多都挺典型的,就像这一道.不过书中的代码写的真是ugly,有很多题目LeetCode上都有,可以去LeetCode讨论区看看,经常有一些大神分享,写的代码真是高效.简洁 ...
- 关于In-App Purchase(内购)的注意事项
前言:关于In-App Purchase(内购)的注意事项 一点注意事项: 内购应该有游客身份的购买选项,不然可能被拒,前一段时间我这边一直是因为没有游客身份购买被拒. 在验证内购结果的时候要注意使用 ...