我目前正在处理的应用程序是一个服务器,它将使用select()管理与客户端的连接,每次服务器收到消息时,它将打开一个新线程以读取套接字。在此期间,套接字的文件描述符将从集合中删除,并将在读取结束时添加。 以下是代码示例
struct s_handle {
int sock;
fd_set * rdfs;
};
int main(){
...
fd_set rdfs;
...
while(1){
....
select(nb_fd,&rdfs,NULL,NULL,NULL)
for_each(peer){
if(FD_ISSET(peer->sock,&rdfs)){
struct s_handle * h = malloc(sizeof(struct s_handle));
h->sock = peer->sock;
h->rdfs = &rdfs;
FD_CLR(peer->sock,&rdfs);
pthread_create(thread,NULL,handle,(void *)&h);
}
}
...
}
...
}
void* handle(void* argss){
struct s_handle * temp = (struct s_handle *) argss;
...
FD_SET(temp->sock,temp->rdfs);
}
FD_SET,FD_ISSET和FD_CLR是原子操作,还是我需要用互斥锁来锁定rdfs?
如果需要互斥锁,我该如何避免死锁?
答案 0 :(得分:3)
首先,你不应该创建那样的线程。创建一个线程是一个相当高的开销操作,只应在需要更多线程时使用,而不仅仅是因为你需要做更多的工作。
是的,您需要使用互斥锁保护FD_*
函数。通常的解决方案是使用一个互斥锁,只保留执行FD_*
操作所需的瞬间。在调用select
之前,您获取互斥锁,复制描述符集,然后释放互斥锁。
一般来说,从读取集中删除套接字是个坏主意。将套接字放回读集中将不会更改稍后已经发生的select
。而且你会弄清楚如何从select
中调出select
的线程来操作新的集合。
您可能想要重新考虑您的I / O发现方法并使用其中一个标准方法,而不是尝试自己动手。你正在强迫将一些套接字没有被收听以进行读取的丑陋权衡,因为最近读取了这些套接字并且select
仍然被阻止或者当你从每个套接字读完时必须重新select
。两种解决方案都不好。
更常见的模式是在读取时将套接字保留在集合中,并且在读取所有套接字(但不一定处理其数据)之前不再返回select
。< / p>
答案 1 :(得分:1)
不能保证它们是,并且你根本不应该依赖它。
fd_set
通常使用普通位图(int
或类似的数组)实现,而FD_*
宏只是位操作操作。这些操作在板形原生int
之外的任何时间都很少是原子的(如果是这样的话)。
答案 2 :(得分:1)
FD_SET
,FD_ISSET
等是宏 - 我认为你不能指望它们是原子的。但是,我建议不要使用互斥锁,而是重新考虑你的方法;每当你需要阅读时创建一个新线程,并从一个不同的线程调用select
而不是使用其结果的线程看起来都是坏主意。如果有一个特殊原因需要使用单独的线程仅用于读取而不是完全处理该连接,那么为什么不让它拥有自己的fd_set
并调用{{1本身?
在重新阅读问题时,我会说如果你做使用现在的方法,你应该注意在每次调用{{{{}}之前需要重建你的阅读select
1}},并由调用修改。因此,线程函数末尾的fd_set
无论如何都没有用,因为如果它还没有准备好再次读取,那么在下一个select
调用中将从集合中删除描述符。您需要在FD_SET
调用之前在select
线程中构建读取集 - 因此您需要另一种方法来确定需要包含哪些套接字。
你可以在你的select
结构中添加一个忙标志,并使用互斥锁保护 - 然后在创建线程之前设置它,并让线程清除它什么时候退出在select select之前填写read select
时,您需要为s_handle
结构中的标记未标记为“忙”的每个对等添加套接字。