Yes but the signal handling code acts as if it is on a different thread. So it cannot access the critical sections or mess up any existing state on the thread anyways. Sure the other parts need to be managed manually but just that should still go a long way. ...Right?
Not quite, by default the signal handler hijacks an existing thread. It is possible to keep a dedicated thread around that solely waits for signals, but that’s a workaround and you end up needing to also mask all signals from all other threads for correctness. And then there are also synchronous signals, which can’t be handled this way (eg. segfaults)
Imagine a scenario where the original thread state is in a critical section, in the middle of allocating memory (which may need a mutex for non-thread local allocations) etc.
The code within the signal handler can’t guarantee access to any shared resource, because the previous execution of the thread may have been in the middle of the critical section. With normal concurrency, the thread that doesn’t hold the mutex can just suspend itself and wait.
However, because the thread has been hijacked by the signal handler, the original critical section cannot complete until the signal has been handled, and the signal handling cannot yield to the original code because it is not suspendable.
Signal handling is distinct from a different thread because it blocks the execution of the “preempted thread” until the signal handler completes.
As a example, if the preempted code grabs a lock for a resource, then signal handler completion can not depend on grabbing that lock because that lock will never be released until the preempted code runs again and the preempted code can never run again until the signal handler completes.
A correct signal handler can never wait for a resource held by regular code. This precludes coordination or sharing via normal locks or critical sections.