3 ms·
Because signals are delivered on context switch, and you're not allowing any of that to happen. Its not a well-behaved process in that case .. not that you sho
by boffinAudio 3y ago
Because signals are delivered on context switch, and you're not allowing any of that to happen. Its not a well-behaved process in that case .. not that you shouldn't be able to just burn as much CPU as you want, just that you would need to open up some space to communicate with the OS in the meantime, and having a signal-handler-thread or a cpu-working-thread is how to do it properly. (Do you also handle SIGHUP? SIGSTOP?)
longjmp() is just asking for hell 6 months later when you come back and have to work out why you did such a hokey thing in the first place.
EDIT: it should be noted that the "side-effect" of using longjmp is .. a context switch .. so you gave yourself some space to behave properly with the OS. Its just that the more future-proof way to do it is to have a thread for work (you have this already with main()) and a thread for signals (you'd just add this to service signals only and give main() a way to cleanly exit) ... Not too complex, and a tad bit easier to read 6 months later ..
- gavinhoward 3y agoUnless you include OS preemption in "context switch," you are wrong; signals can come at any time. If you do include it, then yes, signals only happen on context switch. But in that case, my CPU-bound code is not preventing context switches; the OS preempts it. This is how all major OS's are designed. If I didn't longjmp(), by the way, it could take an infinite amount of time between when the user sent SIGINT to when the program stopped by the very nature of what that utility does. By using longjmp(), a SIGINT becomes a true interrupt, which is what users want. Having a working thread and a signal thread does not change that because the signal thread would need to interrupt the working thread in the same way.
- boffinAudio 3y agoThey can come at any time but they are only delivered to your process by the kernel during a context switch. They're queued until then. > it could take an infinite amount of time between when the user sent SIGINT to when the program stopped by the very nature of what that utility does A well coordinated signal-handling-thread and a workload-thread won't manifest this issue - poorly managed threads however, will. >By using longjmp(), a SIGINT becomes a true interrupt, which is what users want. Hard disagree. What you've done is turned signals into interrupts, which is .. hokey. And not how signals are intended to be used. Its quite possible to get the behaviour you expect - fast interruption and death of work-code - but you'd have to sort your issues with threads out, first. EDIT: its decades-old proven technology: use a semaphore or a mutex to keep your threads in lockstep, and avoid this longjmp() malarkey... signals aren't hard, but maybe they're only just a little less harder than threads ..
- gavinhoward 3y agoI used the system you are advocating in my utility at first. The problem is that you need to constantly check for work. That is expensive on tight loops. Yes, I checked on every loop iteration unless I knew a small, but constant, amount of work had to be done. That's the thing: if you have a tight loop, do you know how long it's going to take to run through everything? In addition, when a SIGINT comes, can you be sure that your state is correct? Here's a test: download my utility and run these commands: $ git checkout 2.7.0 $ ./configure.sh -gO3 $ make $ echo "2^2^32" | bin/bc Then send a SIGINT after a random amount of time. I did that twice, and both times, I triggered asserts. Some state was wrong. This was after I had carefully gone through the entire codebase to check signals everywhere. Now do this: $ git checkout 6.7.0 $ ./configure.sh -gO3 -Sbc.sigint.reset $ make $ echo "2^2^32" | bin/bc And send a SIGINT after a random amount of time. bc exits because its stdin is a pipe, but nothing goes wrong. If you do it like this: $ bin/bc >>> 2^2^32 ^C interrupt (type "quit" to exit) 0: (main) ready for more input >>> And you can merrily continue. Semaphores and mutexes have the same problem: I would need to keep checking them. I understand why you think the way you do; I did too. But the real world is more complicated.
- aa-jv 3y agoIn the 'real world' compare-and-swap operations (such as one would find in atomic types used for communication between worker and handler) are single-cycle operations, if not a hard CPU flag... >I understand why you think the way you do; I did too. But the real world is more complicated. Please consider the complications of the high-end audio world, where such techniques are well established. Not only must bat-out-of-hell threads have all the gumption they can muster, but they have to be able to be controlled - in as close to realtime as possible - by outside handler threads. I think boffinaudio is trying to help you improve your code quality. Its not bad advice to re-think this.
- deleted 3y ago[deleted]
- 3y ago
- actionfromafar 3y agoI think the implicit idea is that the CPU bound thread only is kept busy in smallish chunks, and you’d inject a stop package in its stream of work.
- cesarb 3y agoYou're assuming that the work can be split into "smallish chunks", instead of a single large CPU-bound computation. Sure, "injecting a stop package in its stream of work" is the best design, but only if you do have a stream of work in the first place. Otherwise, it's either signals, or constantly checking a shared "stop" variable in the middle of a very hot CPU-bound loop.
- gavinhoward 3y agoYes, but how do you limit the size of those chunks? Perhaps only do one math operation? Calculating 2^4294967296 is one operation, but it can take minutes (hours?) on my bc. Do you want it to take that long, not responding to SIGINT the entire time? Probably not. Smaller chunks? Then you have to check for signal in a loop, which causes its own problems. I used to do that, and as I told boffinAudio, that caused problems with state being invalid and such.
- deleted 3y ago[deleted]