C可重入函数

时间:2014-05-30 15:59:49

标签: c++ c multithreading

我一直在研究和纠正重入/线程安全,主要是因为我正在实施的一些概念。我想出了一个理论,并且只是想知道是否有任何有重新进入问题经验的人可以提供他们对这个概念的见解和知识......

考虑到主要问题在于通过多个线程调用函数时(并且在解释这些问题时使用的许多示例往往来自在已经执行时调用该方法的信号中断),而该方法是使用本地静态和/或全局数据导致多次调用尝试更改和/或操纵有问题的数据。话虽这么说,创建可重入函数的主要建议是确保不使用这种类型的数据或使用它时具有保护对所述数据的读/写访问的互斥和/或信号量锁定机制。现在锁会生成线程安全函数,但不保证重入函数。现在我知道以下概念违背了我已经阅读的上述建议,但我很想知道直接反对所述建议能否提供一种确保重入函数的有效方法?

int some_func(int someIndexParam)
{
    static bool isActive = false; // start it off as false, no other instances calling it immediately
    if (isActive)
        return ALREADY_RUNNING;
    isActive = true;

    // DO ALL YOUR WORK HERE

    isActive = false;
    return SUCCESS;
}

在上面这个概念中,我正在使用其中一个很大的" no-nos"为了真正确定继续是否安全。现在我知道这可能仍然可能导致竞争条件,在这种罕见的情况下,这种函数几乎背对背地调用同一函数的另一个调用,因为isActive值可能无法及时更新停止继续进行第二次调用,但我认为这是一个小因素,除非这个函数经常被大量的线程调用,但我很想听到其他人对这种处理方法的看法。引人入胜的问题?

由于

4 个答案:

答案 0 :(得分:6)

TL; DR:由于某种原因,它是大禁忌

事实上有多个原因:

  1. 编译器可以自由地重新排列事物,只要所谓的"可观察的行为"由语言指定保留。语言规范允许它完全删除isActive分配。好的编译器会做那个!

  2. 不,它不会导致种族争议, 是一种竞争条件。没有"潜在的"没有"领先"。请记住,计算机每秒能够执行数十亿次操作(甚至是智能手机),成功的程序通常运行时间不止一次,通常在多台计算机上运行。因此,即使某些事件的概率看起来极其遥远,例如曾经万亿次,但几乎可以肯定它会发生,并且会比你想象的更早发生。

  3. 这个代码对于多线程(使用互斥或​​其他正确的同步原语)或可重入的信号处理(使用正确的sig原子类型,并使用正确的单元取消屏蔽等)是错误的。

答案 1 :(得分:2)

你所做的就是创造一个转变mutex。使用互斥锁是保护共享资源的常用且有效的技术。正如您所意识到的那样,实施它们时存在缺陷,可能无法阻止“罕见”情况下的无效访问。不要感到难过,因为那种代码很难做到正确。不要陷入思考并发访问资源的陷阱。像上面这样的问题产生了非常难以调试的问题。神秘失败只发生在随机的星期二或为重要客户演示。

这一切都很困难。过度热心的编译器可能会优化设置您的标志,因为通过代码的所有路径都会取消设置它。为什么额外的工作?声明isActive为volatile会阻止编译器优化您的保护,但这不会阻止可能无序发出写入的硬件。这种保护是“禁忌”的原因是它很难。您必须了解许多特定于平台的问题,例如memory barriersCAS说明。

TL; DR 你有一个很好的想法,但需要更好的实现。

答案 2 :(得分:2)

有趣的想法;但也许还有一些小问题。

首先,假设:

#define true (-1)
#define false (0)

然后问题代码可能适用于具有运行多个线程的单个处理器的系统,该系统仅在遇到阻塞函数时执行(线程)上下文切换。

否则,它有点危险。例如,对于具有单个处理器的系统,该系统在时钟"上执行上下文切换,很可能

       if(isActive)

将由特定线程执行,其中“isActive”'评估为假。但在该线程可以执行之前

       isActive = true;

线程被切换出CPU,允许其他线程运行。如果其中一个线程进入相同的代码路径,它将找到

        if(isActive)

仍然评估为false。现在你在函数中运行了两个线程。

如果问题代码使用了“原子操作”,那么问题代码将是可用的(即使对于多CPU系统)也是如此。这将允许代码使用单个操作执行条件检查和设置操作。虽然原子操作不可移植,但它们可用于大多数所有OS /编译器平台(以各种形式)。 gcc编译器用于提供原子操作作为库函数;但现在他们 'built-in' gcc functions

int some_func(int someIndexParam)
   {
   static bool isActive = false; 

   if(true != __sync_bool_compare_and_swap(&isActive, false, true))
      return ALREADY_RUNNING;


   // DO ALL YOUR WORK HERE

   isActive = false;
   return SUCCESS;
   }

问题代码是一个好的开始。如上所述,编写代码总是更好,以便它完全可重入,不会以任何方式阻塞线程。然而,这并非总是可行的。

答案 3 :(得分:2)

出于这个问题的目的,我假设你不会在信号处理程序中做任何事情,除了可能将事件排队等待以后处理。

然后,如果您的程序是单线程的,则代码完全不需要,应该删除。

如果您的代码是多线程的,那么您的代码就有竞争条件。多个线程可以同时读取active的当前未设置值,然后继续执行不可重入代码。它似乎在测试中运行良好,但它中断,并且可能在最不方便的时候为您的一个客户打破。

您的选择是使用互斥或​​原子操作(测试和设置)进行正确的多线程检查,或者使函数不可重入,这意味着您的应用程序负责不进行可重入调用。< / p>