可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Can anyone tell me how to insert a function call (say Yield()
) at random places inside a C function, so that each time the code is run, Yield()
gets called from different parts of the code ?
I am faced with such a requirement as I'm using 2 threads in a cooperative threading environment , where unless the running thread yields the processor explicitly , the other (waiting) thread cannot start running. I don't want to place the Yield()
call at a single point , since that makes the thread sequence deterministic. Without rewiring the entire environment (from cooperative to pre-emptive) , this is the only solution I can think of in which Thread_1() makes the Yield()
call at random places inside it, allowing Thread_2() to take over.
Any insights into a different solution achieving the same end-goals is also welcome!
回答1:
A BFI solution is called for, I think
I think you will have to solve this the obvious way. You will need to make a wrapper for Yield()
that makes a "random" decision on whether to call the real thing.
If you are not concerned with execution speed then I would make it a real C function, and if you are I might suggest a preprocessor macro.
So, something like:
#define Yield0() ((random() & 0xf) == 0 && Yield())
Choose the mask for the percentage chance of a call you want. For 0xf, and if random()
has good low-order bit randomness, then you would see 1 Yield() in 16 calls. If you can use an MT or other high quality random number generator, the low order bits will be directly useful, otherwise you might want to random() >> 3 & ...
And you will just need to put Yield0() calls everywhere.
回答2:
I'd define a function something like:
void maybe_yield() {
if (rand() & 0x10)
yield();
}
Then sprinkle calls to maybe_yield()
throughout your code. Depending on how often you want yield to be called, you can change 0x10
to a constant with more bits set to get yield()
called more often. Other than that, be sure to call srand()
with a value that changes from one run to the next to get different sequences on different runs.
回答3:
Option A: Why not call yield()
when the thread gets stuck? Better yet, why not encapsulate that in every operation which potentially could get stuck:
int disk_read (...)
{
begin_io ();
while (!io_completed && !timed_out())
yield();
if (timed_out())
// etc.
...
}
Option B: Usually—with cooperative yielding—when the other thread is not ready to run, yield()
is a no-op. Therefore, put it everywhere:
void thread1 (...)
{
yield();
do_something_a();
yield();
do_something_b();
yield();
do_something_c();
...
}
Option C: Trust that processors are plenty fast and waiting for things occurs often enough that minimal yields()
work just fine:
void thread1 (...)
{
init();
while (...)
{
do_heavy_crunching();
yield();
do_something_else();
}
}
In hundreds of real-world applications, Option C works just fine. The determinism usually helps, not hurts.
回答4:
Actually, when you're running in a co-operatively threaded environment, you really do want determinism.
But, if you're hell-bent on doing it, you just need to make it random.
#include <stdlib.h>
// And make sure you seed the generator with srand() somewhere.
#define YIELD_CHANCE 15
#define yield Yield
#ifdef YIELD_CHANCE
#if YIELD_CHANCE > 0
#if YIELD_CHANCE <= 100
#undef yield
void yield(void) {
if (rand() < (RAND_MAX / (100/YIELD_CHANCE)))
Yield();
}
#endif
#endif
#endif
then change your Yield
calls to yield
and, depending on what value YIELD_CHANCE
is set to at compile time, you'll get deterministic or non-deterministic behavior.
If it doesn't exist or is outside the range 1 through 100, yield
will yield all the time. If it's within the range, then it will call the Yield
function randomly, based on the probability you give it.
回答5:
You say you don't want a preprocessor, but it makes it so much easier.
#!/usr/bin/perl
chomp(my $n =<stdin>);
open (my $f, '<', $n);
while (my $l = <$f>) {
print $l;
if ($l =~ /^[\s][^\.]/) {
$r=rand();
if ( int($r*5) == 1 ) {
print "\tcall Yield\n";
}
}
}
This perl script(my first ever) will read a filename from stdin and insert a call randomly into gcc -S generated assembly which can then be compiled easily. It might not work as is for your compiler/arch, but regexes can do almost anything.
A nice addition would be to add a yield always before jump instructions for your processor. This saves you the sprinkling. Finally before jumps you could be using a wrapper function that calls random().