我想等待一个文件描述符和一个互斥锁,推荐的方式是什么?
我想要创建一些线程来执行特定的任务,并使用一个线程安全的队列来和它们进行沟通。同时,我希望在等待的时候能够进行一些文件操作。
有什么推荐的方法来实现这个呢?我是否需要创建一个线程间的管道,当队列从没有元素变成有元素时就往里面写数据?难道没有更好的方法吗?
如果我必须创建这个线程间的管道,那为什么更多实现共享队列的库不允许你把共享队列和线程间的管道作为一个整体来创建呢?
我想问的这个问题,是否意味着我在设计上有根本性的缺陷?
我在问这个问题的时候,主要是关于C++和Python的。我对跨平台的解决方案有一点兴趣,但主要还是关注Linux。
举个更具体的例子……
我有一些代码是用来在文件系统树中搜索东西的。我通过套接字与外部世界保持着几个通信通道。可能会有请求到达,这些请求可能会需要在文件系统树中进行搜索,也可能不需要。
我打算把搜索文件系统树的代码放在一个或多个线程中。我希望把那些需要搜索的请求放到一个线程安全的队列里,让搜索线程来处理。搜索的结果会放到一个完成搜索的队列中。
我希望在搜索进行的同时,能够快速处理所有非搜索的请求。我也希望能够及时处理搜索结果。
处理这些传入请求通常意味着需要某种事件驱动的架构,可能会使用到epoll
。磁盘搜索请求的队列和结果返回的队列意味着需要一个线程安全的队列,可能会用到互斥锁或信号量来实现线程安全。
等待一个空队列的标准方法是使用条件变量。但如果我在等待的时候还需要处理其他请求,那这个方法就不太好用了。要么我就得一直轮询结果队列(这样会导致结果平均延迟半个轮询间隔),要么就会阻塞,无法处理请求。
8 个回答
你提到的是Linux,所以我想说一下:POSIX消息队列可以做到这些,应该能满足你对“内置”功能的要求,虽然可能不太符合你想要的跨平台需求。
线程安全的同步功能是内置的。你可以让你的工作线程在读取队列时阻塞,也就是说,如果队列里没有数据,线程会停下来等着。或者,消息队列还可以使用mq_notify()这个函数,当队列里有新数据时,可以创建一个新线程(或者唤醒一个已经存在的线程)。而且,看起来你会使用select(),消息队列的标识符(mqd_t)可以作为文件描述符在select中使用。
我用你提到的方法解决了这个问题,使用了pipe()和libevent(它是epoll的一个封装)。当工作线程的输出队列从空变为非空时,它会往自己的管道文件描述符(FD)里写一个字节。这样就能唤醒主输入输出线程,主线程就可以获取工作线程的输出。这种方法效果很好,实际上编码起来也非常简单。
在使用事件驱动架构时,我们需要一个统一的方式来报告事件的完成情况。在Linux系统中,如果你在使用文件,就必须用一些特定的工具,比如select或poll,这意味着你需要用管道来处理所有与文件无关的事件。
补充说明: Linux有eventfd和timerfd这两个工具。你可以把它们加入到你的epoll
列表中,这样当它们被另一个线程触发或者在定时事件发生时,就可以跳出epoll_wait
。
还有另一种选择,就是使用信号。你可以用fcntl
来修改文件描述符,这样当文件描述符变得活跃时,就会发出一个信号。信号处理程序可以把一个文件准备好的消息放到你选择的任何类型的队列中。这可以是一个简单的信号量,或者是基于互斥锁/条件变量的队列。因为现在不再使用select
或poll
,所以也不需要用管道来排队与文件无关的消息。
健康提示:我没有尝试过这种方法,虽然我看不出为什么它不行,但我并不清楚使用signal
方法的性能影响。
补充说明:在信号处理程序中操作互斥锁可能是个非常糟糕的主意。