I see people doing this in some parts of heavily multi-threaded, multi-process application systems I work with. It seems to be done around debug lines:
std::cerr << "DEBUG: Reaching: " << __FUNCTION__ << " @ " << __LINE__ << std::endl;
sleep(0);
If I macro out sleep(0); (i.e. change it to ""), the debug output from the system seems to come in different order (less predictable), so I figure it makes the line come out sooner - but I thought std::cerr was unbuffered, and std::endl calls std::flush() anyway, so why would this be开发者_运维知识库?
Basically, it yields control back to the scheduler, and lets you be re-scheduled instantly. That said, it's basically a hack to try and fool the operating system into doing something.
And it's never a good idea to fool the operating system.
If the system is suitably underloaded, then going to sleep will mean the OS gets control and lets the I/O queue flush, so it would have that effect. Sometimes. Depending.
Exactly what it's doing depends on details of the implementation that, frankly, you can't depend upon.
It perturbs the scheduler in a difficult-to-predict way.
Typically the result will be similar to a call to pthread_yield()
- you give up your timeslice. The result of this is that, under heavy debug print load, you'll be less likely to be preempted in the middle of writing to cerr (ie, between those <<
s), since you'll be at the start of a timeslice after the last debug print, and thus you're less likely to have multiple threads overwriting each other's output (ie, getting something like DEBUG: REACHING: foo() @ DEBUG: REACHING bar()17 @ 24
).
That said, this method is unreliable - it's perturbing the scheduler, not asking for specific semantics. It's also slow - unconditionally entering the kernel to yield (and possibly bouncing control among multiple threads too frequently). And it's less likely to work properly on a multicore CPU.
It would be better to put a mutex over all debug output like this. That said, since this is debug code, it's not surprising that the author may have used a quick-and-dirty hack to get it working just well enough to debug whatever problem they were having.
On most platforms, sleep(0)
causes the scheduler to treat the current thread more or less as if it had used up its timeslice. Typically this means that, if possible, another thread in the same process will be scheduled on that core if one is ready-to-run. As you noted, it tends to make scheduling slightly more predictable. To me, that seems counter-productive when debugging.
I agree with Nemo's comment that it's much the same as pthread_yield
. It does have some legitimate uses, but it's sometimes used erroneously as a way to provide more fairness or reduce latency. Typically it just makes performance worse because a context switch blows out the caches.
I've looked at the other answers and I agree with the general sense of befuddlement. My bet is that there is no good reason for it other than that someone thought that more predictable scheduling was a good thing. (It's not. Flexibility is good. The more requirements you put on the scheduler, the more it has to sacrifice to meet those requirements. If you impose requirements that aren't actually required, you trade performance for nothing at all.)
You use this as a way of letting other processes run that may be waiting for some cpu time, but without forcing a particular delay if no other processes are waiting to run.
As others have mentioned, sleep(0)
is a system call that causes the current thread to relinquish control of the CPU.
The only legitimate reason for using this that I can think of is when implementing one's own synchronization primitive such as a spin lock using assembler instructions such as LOCK (x86) and; LDREX and STREX (ARMv7). In such a case, if we encounter a lock we might as well relinquish control immediately in the hope that another thread will do its thing and unlock our lock. This is definitely spinning away until our time slice is up.
Having said that, you really need to know what you are doing when implementing your own synchronization primitive. Specifically, you may need to put in memory barriers to enforce ordering of reads and writes. You are probably much better off using the primitives provided on the platform.
精彩评论