最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - can spinlocks in signal handlers cause deadlock? - Stack Overflow

programmeradmin3浏览0评论

I've read that using a mutex in a signal handler can cause deadlock if a thread of the main program has locked the mutex before the signal handler is invoked. I've also read suggestions that signal handlers should instead use std::atomic types for which is_lock_free() returns true. std::atomic_flag is one such type and cppreference even has a spinlock implementation on its std::atomic_flag page.

However, my question is: if I protect the critical section with a spinlock instead of a mutex in the signal handler and in the main program, aren't deadlocks still be possible? If a thread in the main program locks the spinlock, and then the signal handler runs and tries to lock the spinlock, won't a deadlock occur?

I've read that using a mutex in a signal handler can cause deadlock if a thread of the main program has locked the mutex before the signal handler is invoked. I've also read suggestions that signal handlers should instead use std::atomic types for which is_lock_free() returns true. std::atomic_flag is one such type and cppreference even has a spinlock implementation on its std::atomic_flag page.

However, my question is: if I protect the critical section with a spinlock instead of a mutex in the signal handler and in the main program, aren't deadlocks still be possible? If a thread in the main program locks the spinlock, and then the signal handler runs and tries to lock the spinlock, won't a deadlock occur?

Share Improve this question asked Feb 16 at 11:37 textraltextral 1,0698 silver badges14 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 3

if I protect the critical section with a spinlock instead of a mutex in the signal handler and in the main program, aren't deadlocks still be possible?

Yes, your assessment is correct. A thread can still deadlock itself. More specifically, you cannot wait for a thread to unlock a mutex (or similar) if that thread is unable to run. You could detect that a mutex is locked and then give up but you cannot wait. However, even that is outside specifications with regular mutexes since they are not in the list of async-signal-safe functions.

The advise on atomics is also just a cludge and often more expensive than it needs to be, especially on x86 where regular arithmetic operations with memory operands can do the job but that cannot be enforced in C/C++ or ported to other platforms.

The type that is specifically designed for signals is volatile sig_atomic_t but you can only load and store that (it's usually just a typedef to int). Atomic increments etc. need to be done with atomics. This is because a signal could occur between reading and writing a variable in a read-modify-write operation. The same is true for loading and storing larger objects that may require more than one instruction but those also don't usually come with lock-free atomics. There are some exceptions:

  • Unless you have to worry about another signal handler interrupting the current signal handler, the signal handler itself does not need atomic read-modify-writes etc. After all, the signal handler cannot be interrupted by its main thread
  • Some signals like SIGPIPE only occur during certain system calls and therefore there is no risk of them interrupting other code
  • You can block signals temporarily to update the shared state but that is usually more expensive
  • Some code keeps signals blocked and then unblocks them only during certain system calls such as ppoll so that signal handlers only run when it is safe

The proper way to handle complex cases is to defer work, either to a different thread or back to the main thread. A common pattern is to make a pipe or socketpair. Then write a brief message from the signal handler to the file descriptor. A proper thread (even the thread which ran the signal handler) can then pick up the message from the other file descriptor and process it. This works particularly well in GUI applications because the GUI event loop can handle these messages together with everything else.

In theory you can use this pattern to wait for a response from the receiving thread but don't do that. That thread may still be blocked waiting on a mutex held by the thread that is running the signal handler; another deadlock.

You have to make sure that the message is less than PIPE_BUF (4 kiB) to ensure it is handled atomically.

发布评论

评论列表(0)

  1. 暂无评论