This is what I did: I have one thread whose sole purpose is to wait on children. It's activated by a semaphore. But it will wait and then send a message through a pipe to the thread that started that child when the child exits. (As a side benefit, the launching thread can then use poll() or other fd waiting.)
The killer is killing children; I found that it is best to kill all children that haven't been waited on when killing is necessary. The problem is that child exit messages can block the pipe, so you need to take them out, but they may arrive in a different order than you expect. If you're just killing all children launched by that thread, no problem, just read and ignore every time you kill a child.
There are a lot of details (that code has the single longest code comment I've written in my life), but most of them are in that classic "Gotchas" document matklad references.