我目前正在工作的应用程序是一个服务器,它将使用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_ISSET和FD_CLR是原子操作,还是需要用互斥锁锁定rdfs?
如果需要互斥,如何避免死锁?
发布于 2012-02-27 06:14:21
首先,你不应该创建那样的线程。创建线程是一个相当高的开销操作,应该仅在需要更多线程时使用,而不仅仅是因为您需要做更多工作。
是的,您确实需要使用互斥锁来保护FD_*函数。通常的解决方案是使用一个互斥锁,这个互斥锁只在执行FD_*操作的瞬间保持。在调用select之前,获取互斥锁,复制描述符集,然后释放互斥锁。
不过,一般来说,从读集合中删除套接字并不是一个好主意。将套接字放回读集合中不会改变稍后已经发生的select。您还需要弄清楚如何将调用select的线程从select中提取出来,以便在新的集合上进行操作。
您可能希望重新考虑您的I/O发现方法,并使用其中一个标准方法,而不是尝试使用您自己的方法。您正在进行丑陋的权衡,要么因为最近读取了某些套接字而select仍然被阻塞,而导致某些套接字不被侦听,要么在您完成对每个套接字的读取时必须重新执行select。这两种解决方案都不好。
一种更常见的模式是在读取套接字时将其保留在集合中,直到读取完所有套接字(但不一定处理其数据)后才返回select。
发布于 2012-02-27 06:02:06
不能保证它们是正确的,而且您根本不应该依赖它。
fd_set通常使用普通的位图(int或类似的数组)来实现,而FD_*宏只是位操作操作。这些操作很少在任何比平台原生int更长的操作上是原子的(如果是这样的话)。
发布于 2012-02-27 06:03:41
FD_SET、FD_ISSET等都是宏--我认为你不能指望它们是原子的。但是,我建议您重新考虑您的方法,而不是使用互斥锁;在需要读取时创建一个新线程,并从不同于使用其结果的线程调用select,这两种方法似乎都不太好。如果有一个特殊的原因,为什么您只需要使用单独的线程进行读取,而不是完全处理该连接,那么为什么不让它拥有自己的fd_set并调用select本身呢?
在重读这个问题时,我想说,如果您确实使用了现在的方法,那么您应该注意到,在每次调用select之前,需要重新构建读取的fd_set,并通过调用进行修改。因此,线程函数末尾的FD_SET无论如何都没有用,因为如果描述符没有准备好再次读取,那么它将在下一次select调用时从集合中删除。您需要在select调用之前在select线程中构建读取集--因此您需要另一种确定需要包含哪些套接字的方法。
您可以向s_handle结构添加一个忙标志,并使用互斥锁来保护它--然后在创建线程之前设置它,并在线程退出时将其清除。在select调用之前填充read fd_set时,需要为s_handle结构中未被标志标记为“忙”的每个对等体添加套接字。
https://stackoverflow.com/questions/9457536
复制相似问题