19

I would like to spawn off threads to perform certain tasks, and use a thread-safe queue to communicate with them. I would also like to be doing IO to a variety of file descriptors while I'm waiting.

What's the recommended way to accomplish this? Do I have to created an inter-thread pipe and write to it when the queue goes from no elements to some elements? Isn't there a better way?

And if I have to create the inter-thread pipe, why don't more libraries that implement shared queues allow you to create the shared queue and inter-thread pipe as a single entity?

Does the fact I want to do this at all imply a fundamental design flaw?

I'm asking this about both C++ and Python. And I'm mildly interested in a cross-platform solution, but primarily interested in Linux.

For a more concrete example...

I have some code which will be searching for stuff in a filesystem tree. I have several communications channels open to the outside world through sockets. Requests that may (or may not) result in a need to search for stuff in the filesystem tree will be arriving.

I'm going to isolate the code that searches for stuff in the filesystem tree in one or more threads. I would like to take requests that result in a need to search the tree and put them in a thread-safe queue of things to be done by the searcher threads. The results will be put into a queue of completed searches.

I would like to be able to service all the non-search requests quickly while the searches are going on. I would like to be able to act on the search results in a timely fashion.

Servicing the incoming requests would generally imply some kind of event-driven architecture that uses epoll. The queue of disk-search requests and the return queue of results would imply a thread-safe queue that uses mutexes or semaphores to implement the thread safety.

The standard way to wait on an empty queue is to use a condition variable. But that won't work if I need to service other requests while I'm waiting. Either I end up polling the results queue all the time (and delaying the results by half the poll interval, on average), blocking and not servicing requests.

Omnifarious
  • 50,447
  • 15
  • 117
  • 181
  • You say threads, but then start talking about shared memory and pipes. You you mean you want to fork processes? – Brian Roach Apr 02 '11 at 17:47
  • @Brian Roach - Sorry, that's confusing. I mean threads. I changed 'shared memory queue' to 'thread-safe queue'. – Omnifarious Apr 02 '11 at 17:50
  • `queue` is fine, you just don't have to worry about "shared memory" ... they *are* sharing the same memory - it's a single process. "pipe" on the other hand generally is talking about IPC pipes. Really you just need to read up on how to share data/objects between threads using locks. – Brian Roach Apr 02 '11 at 17:56
  • @Brian Roach - I know how to do that. The problem is that I don't want to block on waiting for a lock because I will also have requests coming in on file descriptors/sockets. – Omnifarious Apr 02 '11 at 18:03

8 Answers8

9

Whenever one uses an event driven architecture, one is required to have a single mechanism to report event completion. On Linux, if one is using files, one is required to use something from the select or poll family meaning that one is stuck with using a pipe to initiate all none file related events.

Edit: Linux has eventfd and timerfd. These can be added to your epoll list and used to break out of the epoll_wait when either triggered from another thread or on a timer event respectively.

There is another option and that is signals. One can use fcntl modify the file descriptor such that a signal is emitted when the file descriptor becomes active. The signal handler may then push a file-ready message onto any type of queue of your choosing. This may be a simple semaphore or mutex/condvar driven queue. Since one is now no longer using select/poll, one no longer needs to use a pipe to queue none file based messages.

Health warning: I have not tried this and although I cannot see why it will not work, I don't really know the performance implications of the signal approach.

Edit: Manipulating a mutex in a signal handler is probably a very bad idea.

doron
  • 24,882
  • 9
  • 58
  • 93
  • That's an interesting idea as well. Thank you. – Omnifarious Apr 02 '11 at 18:49
  • There is one problem to watch out for. It's possible you could get a deadlock condition on stuffing something into the queue if you weren't extra-careful to make sure the right signals weren't blocked in the right threads. For example, getting a signal that puts something on the queue in a thread that's in the middle of taking something off the queue could be pretty not-pleasant. – Omnifarious Apr 02 '11 at 18:56
  • Yip,you will have to block signals while adding to the queue (which is probably involves a syscall) – doron Apr 02 '11 at 19:29
  • I'm picking your answer because it was the alternative method that was the most interesting and unusual. I will probably stick with the pipe though. :-) – Omnifarious Apr 03 '11 at 16:40
  • 1
    Well it did come with a health warning ;) – doron Apr 03 '11 at 22:48
  • @Jean-BernardJansen - I'm thinking that the `futex` call needs to be fixed to be allowed to post an `epoll` event. If you ask it to do this, it should try to wait on the mutex, and return success if it can immediately return, otherwise it should return an error code indicating that it will post the event you asked for when the `futex` becomes available. – Omnifarious Nov 27 '18 at 00:56
  • @Omnifarious or linux could actually implement kqueue which is way better designed than epoll, but I guess it will only happen over Linus dead body – Jean-Bernard Jansen Nov 27 '18 at 17:37
  • eventfd a fantastic little nugget to have learnt about today! whish i knew about it years ago. – Waslap Jul 09 '19 at 05:39
  • Thinking about, manipulating a mutex inside a signal handler is probably a very bad idea – doron Nov 15 '19 at 16:51
5

I've solved this exact problem using what you mention, pipe() and libevent (which wraps epoll). The worker thread writes a byte to its pipe FD when its output queue goes from empty to non-empty. That wakes up the main IO thread, which can then grab the worker thread's output. This works great is actually very simple to code.

twk
  • 15,310
  • 21
  • 68
  • 95
  • Thank you for being the first person to understand my problem! I was hoping to avoid the pipe. It seems kind of kludgey, but if that's how it has to be done, so be it. – Omnifarious Apr 02 '11 at 18:25
  • @Omnifarious - I understood your problem. I may not have explained myself clearly. With MQs you don't *need* the extra pipe. You put the mqd_t of your worker output queue in your select set. When one of the workers puts something in the output queue your main loop will be notified. – Duck Apr 02 '11 at 18:36
  • Just make sure you make your file descriptor non-blocking. The MQ stuff looks neat -- I'd be interested to know if there are any problems hooking it into libevent. – twk Apr 02 '11 at 19:32
  • I never used libevent but if it just wraps epoll then it should work. Man (7) mq_overview specifically says "On Linux, a message queue descriptor is actually a file descriptor, and can be monitored using select(2), poll(2), or epoll(7). This is not portable." – Duck Apr 02 '11 at 20:35
  • I was tempted to pick yours because this is what I'm likely actually going to do. It has the advantage of only requiring system calls in certain specific situations (sometime after non-empty queue becomes empty). And those situations are less (not more) likely to occur during times of high load. But I picked the most unusual and interesting alternate method instead. – Omnifarious Apr 03 '11 at 16:50
4

You have the Linux tag so I am going to throw this out: POSIX Message Queues do all this, which should fulfill your "built-in" request if not your less desired cross-platform wish.

The thread-safe synchronization is built-in. You can have your worker threads block on read of the queue. Alternatively MQs can use mq_notify() to spawn a new thread (or signal an existing one) when there is a new item put in the queue. And since it looks like you are going to be using select(), MQ's identifier (mqd_t) can be used as a file descriptor with select.

Duck
  • 25,228
  • 3
  • 57
  • 86
  • Actually, thinking about it, this might work. I was thinking I'd have to serialize all of my data. But I can just dump a raw pointer into the queue. Though this will make reference counted memory management quite interesting. :-) And, of course, I could just dump raw pointers into a pipe as well. – Omnifarious Apr 02 '11 at 18:45
  • If you are in the same process, you can pass a raw pointer provided this signifies a transfer of ownership to the new thread (the old thread should no longer be allowed to access the data). If you are working cross process, the serialization becomes important. Bear in mind that, like pipes, message queues are fully fledged kernel cross process kernel objects,so, unlike other ITC mechanisms, any access to them will involve a syscall. – doron Apr 02 '11 at 19:02
  • @doron I was thinking Omnifarious was doing so much file and network i/o that the syscalls would be irrelevant. Of course I was also thinking he was just returning a filename or such, not a complex structure or variable array. You are pretty much forced to pass pointers (in-process) then regardless if it is an MQ or hand built. – Duck Apr 02 '11 at 19:19
  • @Duck: I'm returning a complex structure. But I could just return filenames and have the main thread read the complex structure. I think though, that would significantly reduce the usefulness of having the filesystem IO in another thread. I'm using the filesystem as a key-value store. Eventually I may also use eventually a DHT and/or a NoSQL database. And the fs access thread may have to pass back "I dunno, get it from who you're talking to." – Omnifarious Apr 02 '11 at 20:07
  • You had an interesting method too. Make the queue into a file descriptor so you don't have two fundamentally different kinds of objects. I do not think much of POSIX message queues though. All the POSIX IPC mechanisms suffer from the problem of creating persistent resources that have no names in the filesystem and require special tools (like the `ipcs` command) to manage. The only thing message queues really offer over pipes is this persistence and preservation of message boundaries. – Omnifarious Apr 03 '11 at 16:44
  • @Omnifarious - I was just throwing it out. But you have POSIX MQs confused with SYSV MQs. They can create names in the file system (and be used with rm and so forth). Anyway, good luck with the project. – Duck Apr 04 '11 at 12:01
  • @Duck - Interesting. I didn't know there was a difference between POSIX MQs and Sys V MQs. I will have to research more on that. I thank you for throwing it out. It's an interesting idea. – Omnifarious Apr 04 '11 at 15:03
3

Duck's and twk's are actually better answers than doron's (the one selected by the OP), in my opinion. doron suggests writing to a message queue from within the context of a signal handler, and states that the message queue can be "any type of queue." I would strongly caution you against this since many C library/system calls cannot safely be called from within a signal handler (see async-signal-safe).

In particuliar, if you choose a queue protected by a mutex, you should not access it from a signal handler. Consider this scenario: your consumer thread locks the queue to read it. Immediately after, the kernel delivers the signal to notify you that a file descriptor now has data on it. You signal handler runs in the consumer thread, necessarily), and tries to put something on your queue. To do this, it first has to take the lock. But it already holds the lock, so you are now deadlocked.

select/poll is, in my experience, the only viable solution to an event-driven program in UNIX/Linux. I wish there were a better way inside a mutlithreaded program, but you need some mechanism to "wake up" your consumer thread. I have yet to find a method that does not involve a system call (since the consumer thread is on a waitqueue inside the kernel during any blocking call such as select).

EDIT: I forgot to mention one Linux-specific way to handle signals when using select/poll: signalfd(2). You get a file descriptor you can select/poll on, and you handling code runs normally instead of in a signal handler's context.

Alexis Wilke
  • 15,168
  • 8
  • 60
  • 116
Paul Coccoli
  • 497
  • 1
  • 4
  • 13
  • I mention this in the comments. You would have to block the signal in question before acquiring the mutex. I should perhaps have chosen @twk's answer because it's the one I actually used. Though signalfd is nice to learn about. :-) – Omnifarious Feb 26 '12 at 19:32
  • Wow! `signalfd()` is marvelous! I always wondered why the `signal()` interface was still so crappy under Unices, but with `signalfd()`, it becomes a breeze. – Alexis Wilke Nov 10 '15 at 14:36
2

It seems nobody has mentioned this option yet:

Don't run select/poll/etc. in your "main thread". Start a dedicated secondary thread which does the I/O and pushes notifications into your thread-safe queue (the same queue which your other threads use to communicate with the main thread) when I/O operations complete.

Then your main thread just needs to wait on the notification queue.

Alex D
  • 28,136
  • 5
  • 72
  • 115
2

This is a very common seen problem, especially when you are developing network server-side program. Most Linux server-side program's main look will loop like this:

epoll_add(serv_sock);
while(1){
    ret = epoll_wait();
    foreach(ret as fd){
        req = fd.read();
        resp = proc(req);
        fd.send(resp);
    }
}

It is single threaded(the main thread), epoll based server framework. The problem is, it is single threaded, not multi-threaded. It requires that proc() should never blocks or runs for a significant time(say 10 ms for common cases).

If proc() will ever runs for a long time, WE NEED MULTI THREADS, and executes proc() in a separated thread(the worker thread).

We can submit task to the worker thread without blocking the main thread, using a mutex based message queue, it is fast enough.

epoll_add(serv_sock);
while(1){
    ret = epoll_wait();
    foreach(ret as fd){
        req = fd.read();
        queue.add_job(req); // fast, non blockable
    }
}

Then we need a way to obtain the task result from a worker thread. How? If we just check the message queue directly, before or after 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
    }
}

However, the checking action will execute after epoll_wait() to end, and epoll_wait() usually blocks for 10 micro seconds(common cases) if all file descriptors it waits are not active.

For a server, 10 ms is quite a long time! Can we signal epoll_wait() to end immediately when task result is generated?

Yes! I will describe how it is done in one of my open source project:

Create a pipe for all worker threads, and epoll waits on that pipe as well. Once a task result is generated, the worker thread writes one byte into the pipe, then epoll_wait() will end in nearly the same time! - Linux pipe has 5 us to 20 us latency.


In my project SSDB(a Redis protocol compatible in-disk NoSQL database), I create a SelectableQueue for passing messages between the main thread and worker threads. Just like its name, SelectableQueue has an file descriptor, which can be wait by epoll.

SelectableQueue: https://github.com/ideawu/ssdb/blob/master/src/util/thread.h#L94

Usage in main thread:

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);
        }
    }
}

Usage in worker thread:

fd, req = queue->pop_task();
resp = proc(req);
queue->add_result(fd, resp);
ideawu
  • 2,081
  • 1
  • 21
  • 27
  • Yeah. :-( I was hoping to avoid this, but it seems the thing to do. Someone I talked to said that at one point there was a version of the `futex` call that took a file descriptor and `epoll` could wait on this file descriptor for the `futex` to be unblocked. It would've been nice if this had been added, like the `signalfd` system call. – Omnifarious Sep 14 '17 at 11:59
  • @Omnifarious It looks like there was once a FUTEX_FD option to the futex syscall which allowed associating a fd with a futex, thus facilitating wait's on multiple futexes, etc. via epoll_wait et. al., but it was retired because, as the futex manpage says, it was "inherently racy". – PSkocik Sep 26 '19 at 12:13
  • @PSkocik - I believe Valve is trying to get a change to the `futex` call made to support something like this now because of Proton (aka WINE for games). A lot of Windows programs rely on LOTS of events, and modeling each one with it's own fd runs the process out of fds. So they need a better way of handling them that still makes it easy to wait on several at once. – Omnifarious Sep 26 '19 at 20:37
  • @Omnifarious I'm fairly certain at this point I'll be using eventfd/socketpair/pipe based semaphores/condvars because of the possibility of polling on them along with other fds. Semaphores and condvars are for long-time waits anyway and for that futexes seem like an overkill that just avoids a sleep and a syscall at the very last moment at the cost of lost pollability. Better to not microoptimize like that and simply spin-wait for a while if sleep avoidance is so important. – PSkocik Sep 26 '19 at 21:26
1

C++11 has std::mutex and std::condition_variable. The two can be used to have one thread signal another when a certain condition is met. It sounds to me like you will need to build your solution out of these primitives. If you environment does not yet support these C++11 library features, you can find very similar ones at boost. Sorry, can't say much about python.

Howard Hinnant
  • 179,402
  • 46
  • 391
  • 527
0

One way to accomplish what you're looking to do is by implementing the Observer Pattern

You would register your main thread as an observer with all your spawned threads, and have them notify it when they were done doing what they were supposed to (or updating during their run with the info you need).

Basically, you want to change your approach to an event-driven model.

Brian Roach
  • 72,790
  • 10
  • 128
  • 154
  • Wouldn't that result in the Observer notifications being run in the context of the thread that was doing the work? Wouldn't that mean that any object accessed by the notification would also have to be thread safe so the main thread and worker thread didn't end up stomping on each other? – Omnifarious Apr 02 '11 at 18:07
  • The method in the main thread being called by the spawned ones would need to have a lock (which is owned by the main thread), but you're only locking for the time it takes to do whatever you need to in that method. – Brian Roach Apr 02 '11 at 18:16
  • It just comes down to that there's no free lunch in thread synchronization :) You can minimize contention as much as possible (when possible) using non-blocking calls/lock checks ... but in the end if you need to modify something in more than one thread, you need to protect it somehow. – Brian Roach Apr 02 '11 at 18:20