【问题标题】:How to call a C function from random places inside another function?如何从另一个函数内的随机位置调用 C 函数?
【发布时间】:2010-12-25 13:08:33
【问题描述】:

谁能告诉我如何在 C 函数内的随机位置插入一个函数调用(比如 Yield() ),这样每次运行代码时,Yield() 都会从不同的地方调用部分代码?

我面临这样的要求,因为我在协作线程环境中使用 2 个线程,除非正在运行的线程显式地让出处理器,否则另一个(等待)线程无法开始运行。我不想将Yield() 调用放在一个点上,因为这会使线程序列具有确定性。在不重新连接整个环境(从合作到先发制人)的情况下,这是我能想到的唯一解决方案,其中 Thread_1() 在其中的随机位置进行 Yield() 调用,从而允许 Thread_2() 接管。

也欢迎对实现相同最终目标的不同解决方案的任何见解!

【问题讨论】:

  • 当您说“每次运行代码”时,您是指“运行”还是“编译”?另外,“Yield()”是做什么的,为什么要在随机位置插入它,周围的代码是做什么的(数学?打印东西?等等)?
  • 如果你想实现协程,请查看stackoverflow.com/questions/1635827/… 了解一些想法
  • +1 链接! @Chris,理想情况下,我更喜欢“运行” - 只有在完全不可能的情况下,然后“编译”。 Yield() 用于将处理器控制权交给任何其他等待线程 - 这是强制线程放弃控制权的唯一方法(对我可用)。周围的代码实际上正在执行一组操作,这些操作会改变硬件中的某些内容——但我认为这没有任何意义;我们可以假设我们正在做一些复杂的计算!

标签: c multithreading random c-preprocessor


【解决方案1】:

我认为需要 BFI 解决方案

我认为你必须以显而易见的方式解决这个问题。您需要为 Yield() 制作一个包装器,以便“随机”决定是否调用真实的东西。

如果您不关心执行速度,那么我会将其设为真正的 C 函数,如果您愿意,我可能会建议使用预处理器宏。

所以,类似:

#define Yield0() ((random() & 0xf) == 0 && Yield())

根据您想要的通话机会百分比选择掩码。对于 0xf,如果 random() 具有良好的低位随机性,那么您将在 16 次调用中看到 1 Yield()。如果你可以使用 MT 或其他高质量的随机数生成器,低位将直接有用,否则你可能想random() >> 3 & ...

您只需要在任何地方调用 Yield0()。

【讨论】:

  • 我可能会问一个非常幼稚的问题 - 是否有可能通过一些预处理宏将 Yield() 调用“放置”在代码中的随机位置?一个更好(甚至更雄心勃勃/天真)的解决方案是每次代码“运行”时,从代码中的随机位置调用 Yield()。有什么想法吗?
  • 为此,您必须在汇编中编写自修改代码。否则你所要求的几乎是不可能的。
  • 哇 - 我很喜欢它!你能给我一些关于如何去做的指针/链接吗?我以前从未这样做过,但与所有事情一样,总是有第一次!
  • 在这里自行修改代码无济于事。自我修改比人们建议的 fgetc(urandom) 更昂贵,因为您必须从某个地方获得随机性,并且二进制 grep 您的代码还必须位于 w|x 页面中。如果您只想按照您所说的那样在随机位置获得收益(尽管我对此表示怀疑),那么您最好的解决方案是为您的代码编写一个简单的预处理器,用 do_nothing() 随机替换 yield()。
  • 不幸的是,编写预处理器不是一种选择——这将占用比分配更多的时间。 C 预处理器不能用于这个吗?
【解决方案2】:

我会定义一个类似的函数:

void maybe_yield() { 
    if (rand() & 0x10)
        yield();
}

然后在整个代码中调用maybe_yield()。根据您希望调用 yield 的频率,您可以将 0x10 更改为设置更多位的常量,以更频繁地调用 yield()。除此之外,请务必调用 srand() 并使用从一次运行到下一次更改的值,以便在不同运行时获得不同的序列。

【讨论】:

    【解决方案3】:

    选项A:当线程卡住时,为什么不调用yield()?更好的是,为什么不将它封装在每个可能会卡住的操作中:

    int disk_read (...)
    {
        begin_io ();
        while (!io_completed  &&  !timed_out())
             yield();
        if (timed_out())
            // etc.
         ...
    }
    

    选项 B:通常——通过协作让步——当另一个线程还没有准备好运行时,yield() 是一个空操作。因此,把它放在任何地方:

    void thread1 (...)
    {
        yield();
        do_something_a();
        yield();
        do_something_b();
        yield();
        do_something_c();
        ...
    }
    

    选项 C:相信处理器足够快,并且等待事情发生的频率足够高,以至于最小的 yields() 工作正常:

    void thread1 (...)
    {
        init();
        while (...)
        {
            do_heavy_crunching();
            yield();
            do_something_else();
        }
    }
    

    在数百个实际应用程序中,选项 C 运行良好。确定性通常是有帮助的,而不是有害的。

    【讨论】:

    • +1 不错的综合答案 - 但我真正感兴趣的是选项 B 的变体 - 不同之处在于它是预处理器在随机点“喷洒”对 yield() 的调用在代码中,模拟一个先发制人的行为。我同意协作线程在(完全)消除同步问题方面非常有用,但我试图(通过我的测试)模拟的是在现实世界的多线程操作系统中可能发生的情况,因此我的努力!!
    【解决方案4】:

    实际上,当您在协作线程环境中运行时,您确实确实需要确定性。

    但是,如果你下定决心要这样做,你只需要让它随机。

    #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
    

    然后将您的Yield 调用更改为yield,根据编译时YIELD_CHANCE 设置的值,您将获得确定性或非确定性行为。

    如果它不存在或超出 1 到 100 的范围,yield 将一直让步。如果在范围内,它会根据你给它的概率随机调用Yield函数。

    【讨论】:

    • 不错的宏——但它仍然让我有责任在代码中到处“洒”这个宏(1000行!!)——所以我的问题是,预处理器可以随机放置这个宏代码中的点?
    • 不,它不能。您必须编写自己的自己的预处理器,并确保它足够智能,不会将它们放在错误的位置。
    【解决方案5】:

    你说你不需要预处理器,但它让它变得容易多了。

       #!/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";
                }
            }
        }
    

    这个 perl 脚本(我的第一个脚本)将从标准输入读取文件名并将调用随机插入 gcc -S 生成的程序集,然后可以轻松编译。它可能不适用于您的编译器/架构,但正则表达式几乎可以做任何事情。

    一个很好的补充是总是在处理器的跳转指令之前添加一个 yield。这可以为您节省洒水。最后,在跳转之前,您可以使用调用 random() 的包装函数。

    【讨论】:

    • 感谢您的脚本-您说得对,它不能按原样使用,但我会尝试一下...
    • 在汇编中这样做的好处之一是理论上它更有可能在不方便的地方跳转,因此您可以获得真正的线程状行为。另一个是即使在最坏的情况下,您也只需检查处理器中的所有操作码。做一些类似于 C 源代码或机器代码的事情需要更多的工作。
    猜你喜欢
    • 2016-09-20
    • 2015-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-09
    • 1970-01-01
    • 1970-01-01
    • 2011-05-30
    相关资源
    最近更新 更多