5 ms·
fork() is rarely ideal, unless followed by exec() to launch another processes. That copy-on-write thing often wastes too much physical memory. I think threads a
by Const-me 5y ago
fork() is rarely ideal, unless followed by exec() to launch another processes. That copy-on-write thing often wastes too much physical memory. I think threads are usually better, the shared memory allows to marshal large volumes of data without making copies.
I like poll() much better than signals. Signals are too limiting, and too arcane. It's much easier to use poll() and dispatch things manually based on the signaled handles, at least there's a guarantee your code won't be interrupted by another event.
Pretty much all modern kernel things are compatible with poll. There's eventfd() which acts as an event or semaphore, message queues (but not the ones from the article, better ones, created with mq_open not msgget), Unix domain sockets for cross-process messaging. There's more, poll() can track termination of processes (see pidfd_open), page flip events in a GPU with DRM, consumed or available video/audio samples with V4L2 or ALSA.
- kragen 5y agoI partly agree, but a lot of this stuff is very context-dependent. Signals are interrupts, which are faster than an event loop but very bug-prone, especially in C (few C library functions are safe in interrupts), but shared-memory threads are too, for almost exactly the same reasons. If you want to share memory between processes, it's easy enough to use mmap(). Both threads and signals are somewhat tamer in Python, but lose some of their advantages on the way. Interrupts are really needed for three reasons: device driver latency and buffering (multithreaded CPUs probably being a superior alternative), killing infinite loops, and userspace virtual memory. Unix signals are useless for the first and arguably so bad at the other two that Unix would have been better off without them. Generally running multiple processes of the same program with different exec() calls uses more memory than forking it at startup, not less. It's true that threads use less still, and event loops less than that, when you don't have a multicore scaling problem. If you're looking at writing your own event loop, select() is more convenient than poll(), and io_uring is worth checking out. kqueue, iocp, and epoll are faster than poll(). Also though maybe just try libev, libevent, or libuv instead of writing your own.
- IiydAbITMvJkqKf 5y agoNEVER use select(). It's like gets() - an API that should never be called in new code. poll is the standardized alternative (epoll/kqueue are more performant with large numbers of fds, but are os-dependent). The biggest problem with select() is that it will corrupt your stack if you use it with an fd with a value >= 1024. Since you can't easily control fd values, you must assume that calling select() is UB unless explicitly proven otherwise. There are also performance problems due to having to set up the sets before each call, but these are minor in comparison.
- kragen 5y agoYou can easily control fd values, and attackers can't control them, so even if you're using fd_set, it's not like gets(). You can use select() safely by dynamically allocating your bitvectors, assuming you're using a sensible kernel instead of one that only cares about standards-compliance. Or you could just not have thousands of open files in a single process, which is the way every single Unix program worked for the first 25 years of Unix. I have 269 processes running on this machine right now and none of them is using a file descriptor greater than 255. Even Firefox is only using up to fd 75. The worst offender is gnome-terminal, using fds up to 226, and its children. Having thousands of simultaneously open files is a useful way to structure a few specialized programs like chat servers and load balancers, but it's not something most programs need to worry about. Of course in a library the situation is different, but you probably shouldn't be calling poll() or select() in a library unless it's something like libevent, libev, or libuv, because you can't use two such libraries in the same program unless you're willing to suffer multithreading.
- Const-me 5y ago> If you want to share memory between processes, it's easy enough to use mmap() It's only easy when all of the following is true: (1) sharing one time only (2) Sharing exactly one continuous buffer, as opposed to anything more complicated with pointers inside (3) Sharing between processes one-to-one. When any of these things is false, mmap becomes hard. For instance, despite C++ std::vector is a continuous buffer, when sharing a mutable one that grow/reallocate thing gonna cause issues. > select() is more convenient than poll() Strongly disagree. These macros and the bitmap is pure BS. For some handle types like sockets, for hardware reasons (multicore CPUs) Linux no longer guarantees sequential file descriptors. Even the manual page says "WARNING: select() can monitor only file descriptors numbers that are less than FD_SETSIZE (1024) - an unreasonably low limit for many modern applications - and this limitation will not change. All modern applications should instead use poll(2) or epoll(7)" poll() is awesome in comparison, the API with that array of simple structures is perfect for the job. At least in terms of usability, AFAIK epoll does better performance-wise. > io_uring is worth checking out Right, but the use case is different, it's only for I/O. > kqueue, iocp Not available on Linux? > epoll Yeah, but I think to truly benefit from this one, need multithreading. Multithreaded async I/O is hard, I think people only doing that when they absolutely have to for performance reasons. > just try libev, libevent, or libuv instead of writing your own My use cases are probably different. I rarely using poll() for network sockets, it's usually other things: process and threads synchronization, timers with timerfd(), hardware devices (GPU, sound card, hardware video codecs).
- friend-monoid 5y agoSignals have some wierd properties; a process-directed signal (i.e. the usual one you’d send with kill) chooses an arbitrary thread and invokes the signal handler. You can get in to some weird situations with signals and threads. If you’re using signals as an ipc mechanism, a useful tip is to just block the signal and register a signalfd on poll/epoll to regain control flow control.
- colonwqbang 5y agoThe classic solution would be to fork a special signal catching thread and play with the signal masks to ensure the signal goes there. This is what glib does if I recall correctly. ... Which of course seems very silly today. Signalfd is a much nicer API to use for new programs!