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.