我想在文件描述符和互斥锁上等待,建议的方法是什么?

我想生成线程来执行某些任务,并使用线程安全队列与它们进行通信。在我等待的时候,我也想对各种文件描述符做IO。 推荐的方法是什么?我是否必须创建一个线程间管道并在队列从无元素转到某些元素时写入它?有没有更好的方法? 如果我必须创建线程间管道,为什么实现共享队列的更多库不允许您将共享队列和线程间管道创建为单个实体? 我想这样做的事实是否意味着一个基本的设计缺陷? 我问这个关于C ++和Python的问题。我对跨平台解决方案略感兴趣,但主要对Linux感兴趣。 更具体的例子...... 我有一些代码将搜索文件系统树中的东西。我有几个通过套接字向外界开放的通信渠道。可能(或可能不)导致需要搜索文件系统树中的内容的请求将到达。 我将隔离在一个或多个线程中搜索文件系统树中的东西的代码。我想接受这样的请求,这些请求导致需要搜索树并将它们放入由搜索器线程完成的线程安全队列中。结果将被放入已完成搜索的队列中。 我希望能够在搜索过程中快速为所有非搜索请求提供服务。我希望能够及时对搜索结果采取行动。 处理传入的请求通常意味着某种使用
epoll
的事件驱动架构。磁盘搜索请求队列和结果返回队列意味着一个线程安全队列,它使用互斥锁或信号量来实现线程安全。 等待空队列的标准方法是使用条件变量。但是,如果我需要在等待期间提供其他请求,那将无效。我最终一直在轮询结果队列(并且平均将结果延迟轮询间隔的一半),阻塞而不是服务请求。     
已邀请:
每当使用事件驱动的体系结构时,需要一个机制来报告事件完成。在Linux上,如果使用文件,则需要使用select或poll族中的某些内容,这意味着使用管道来启动所有无文件相关事件。 编辑:Linux有eventfd和timerfd。这些可以添加到你的
epoll
列表中,用于分别从另一个线程或定时器事件触发时跳出
epoll_wait
。 还有另一种选择,那就是信号。可以使用
fcntl
修改文件描述符,以便在文件描述符变为活动时发出信号。然后,信号处理程序可以将文件就绪消息推送到您选择的任何类型的队列上。这可能是一个简单的信号量或互斥/ condvar驱动的队列。由于现在不再使用
select
/
poll
,因此不再需要使用管道来排队基于文件的消息。 健康警告:我没有尝试过,虽然我不明白为什么它不起作用,但我真的不知道
signal
方法的性能影响。     
我已经使用你提到的,pipe()和libevent(包装epoll)解决了这个确切的问题。当工作线程的输出队列从空变为非空时,工作线程将一个字节写入其管道FD。这会唤醒主IO线程,然后可以获取工作线程的输出。这项工作很棒实际上非常简单的代码。     
你有Linux标签,所以我要抛弃它:POSIX消息队列完成所有这些,如果不是你不太希望的跨平台愿望,它应该满足你的“内置”请求。 线程安全同步是内置的。您可以让工作线程阻塞读取队列。或者,当队列中有新项目时,MQ可以使用mq_notify()来生成新线程(或发信号通知现有线程)。因为看起来你将使用select(),所以MQ的标识符(mqd_t)可以用作带select的文件描述符。     
在我看来,Duck和twk实际上是比doron(由OP选择的那个)更好的答案。 doron建议从信号处理程序的上下文中写入消息队列,并声明消息队列可以是“任何类型的队列”。我强烈提醒您不要这样做,因为许多C库/系统调用无法从信号处理程序中安全地调用(请参阅async-signal-safe)。 特别是,如果选择受互斥锁保护的队列,则不应从信号处理程序访问它。请考虑以下情形:您的使用者线程锁定队列以读取它。紧接着,内核发出信号通知您文件描述符现在有数据。您必须在消费者线程中发出处理程序运行信号,并尝试在您的队列中放置一些内容。要做到这一点,首先必须采取锁定。但它已经锁定了,所以你现在陷入僵局。 根据我的经验,select / poll是UNIX / Linux中事件驱动程序唯一可行的解​​决方案。我希望在mutlithreaded程序中有更好的方法,但你需要一些机制来“唤醒”你的消费者线程。我还没有找到一个不涉及系统调用的方法(因为在任何阻塞调用期间,例如select,消费者线程在内核中的等待队列中)。 编辑:我在使用select / poll:signalfd(2)时忘了提到一种特定于Linux的处理信号的方法。您将获得一个可以选择/轮询的文件描述符,并且您处理的代码可以正常运行,而不是在信号处理程序的上下文中运行。     
似乎没人提到这个选项: 不要运行
select
/
poll
/ etc。在你的“主线程”中。在I / O操作完成时,启动一个专用的辅助线程来执行I / O并将通知推送到线程安全队列(与其他线程用来与主线程通信的队列)。 然后你的主线程只需要等待通知队列。     
C ++ 11有std :: mutex和std :: condition_variable。当满足某个条件时,这两个可用于使一个线程信号另一个。听起来,你需要从这些原语中构建解决方案。如果您的环境尚不支持这些C ++ 11库特性,那么您可以在boost中找到非常类似的特性。对不起,关于python不能说太多。     
这是一个非常常见的问题,尤其是在开发网络服务器端程序时。大多数Linux服务器端程序的主要外观将像这样循环:
epoll_add(serv_sock);
while(1){
    ret = epoll_wait();
    foreach(ret as fd){
        req = fd.read();
        resp = proc(req);
        fd.send(resp);
    }
}
它是单线程(主线程),基于epoll的服务器框架。问题是,它是单线程的,而不是多线程的。它要求proc()永远不会阻塞或运行很长时间(例如,对于常见情况,为10毫秒)。 如果proc()将运行很长时间,我们需要MULTI THREADS,并在一个单独的线程(工作线程)中执行proc()。 我们可以在不阻塞主线程的情况下向工作线程提交任务,使用基于互斥锁的消息队列,它足够快。
epoll_add(serv_sock);
while(1){
    ret = epoll_wait();
    foreach(ret as fd){
        req = fd.read();
        queue.add_job(req); // fast, non blockable
    }
}
然后我们需要一种从工作线程获取任务结果的方法。怎么样?如果我们只是在epoll_wait()之前或之后直接检查消息队列。
epoll_add(serv_sock);
while(1){
    ret = epoll_wait(); // may blocks for 10ms
    resp = queue.check_result(); // fast, non blockable
    foreach(ret as fd){
        req = fd.read();
        queue.add_job(req); // fast, non blockable
    }
}
但是,检查操作将在epoll_wait()结束后执行,而epoll_wait()通常会阻塞10微秒(常见情况),如果它等待的所有文件描述符都不活动。 对于服务器,10毫秒是相当长的时间!我们可以发信号通知epoll_wait()在生成任务结果时立即结束吗? 是!我将在我的一个开源项目中描述它是如何完成的: 为所有工作线程创建管道,epoll也在该管道上等待。生成任务结果后,工作线程将一个字节写入管道,然后epoll_wait()将在几乎相同的时间内结束! - Linux管道有5到20美元的延迟。 在我的项目SSDB(兼容Redis协议的磁盘NoSQL数据库)中,我创建了一个SelectableQueue,用于在主线程和工作线程之间传递消息。就像它的名字一样,SelectableQueue有一个文件描述符,可以通过epoll等待。 SelectableQueue:https://github.com/ideawu/ssdb/blob/master/src/util/thread.h#L94 在主线程中的用法:
epoll_add(serv_sock);
epoll_add(queue->fd());
while(1){
    ret = epoll_wait();
    foreach(ret as fd){
        if(fd is queue){
            sock, resp = queue->pop_result();
            sock.send(resp);
        }
        if(fd is client_socket){
            req = fd.read();
            queue->add_task(fd, req);
        }
    }
}
工作线程中的用法:
fd, req = queue->pop_task();
resp = proc(req);
queue->add_result(fd, resp);
    
完成你要做的事情的一种方法是实现观察者模式 您将主线程注册为包含所有衍生线程的观察者,并让它们在完成它们应该执行的操作时通知它(或在运行期间使用您需要的信息进行更新)。 基本上,您希望将方法更改为事件驱动模型。     

要回复问题请先登录注册