【问题标题】:C++ macro in scope of if statement not compilingif 语句范围内的 C++ 宏未编译
【发布时间】:2018-01-02 13:27:17
【问题描述】:

我有一些代码应该是线程安全的 python/c++ api。我正在使用宏Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS,它们展开以创建保存线程状态并创建锁。我在方法退出之前释放锁;一次在 if 语句范围内,一次在方法范围内。

为什么不能编译?它会在第二个 Py_END_ALLOW_THREADS 宏处生成错误:error: _save was not declared in this scope

uint8_t SerialBuffer::push_msg() {

#if defined (UBUNTU)
  Py_BEGIN_ALLOW_THREADS
#endif

  if (_type == ARRAY) {
      // array access
  } else if (_type == PRIORITY_QUEUE) {
      // queue access
  } else {

    // Placing the return statement in the preprocessor directive
    // has no effect.
#if defined (UBUNTU)
    Py_END_ALLOW_THREADS
#endif

    return FAIL;
  }

#if defined (UBUNTU)
  Py_END_ALLOW_THREADS
#endif

  return SUCCESS;
}

我还尝试将return 语句放在#if 指令范围内,这会产生相同的错误。但是,这是可行的:

uint8_t SerialBuffer::push_msg() {

#if defined (UBUNTU)
  Py_BEGIN_ALLOW_THREADS
#endif

  if (_type == ARRAY) {
      // array access
  } else if (_type == PRIORITY_QUEUE) {
      // queue access
  } else {
    // NOTE lack of #if directive here.
    // Even though if this code executes the code below will not.
    // Seems like a relatively simple problem for lambda calculus, no?
    return FAIL;
  }

#if defined (UBUNTU)
  Py_END_ALLOW_THREADS
#endif

  return SUCCESS;
}

编辑:我知道第二个示例不进行线程清理;但是,它可以编译。

编辑2: Py_BEGIN_ALLOW_THREADS 扩展为 { PyThreadState *_save; _save = PyEval_SaveThread();

Py_END_ALLOW_THREADS 扩展为 PyEval_RestoreThread(_save); } 注意在BEGIN 前面和END 前面的范围括号。为什么宏扩展包含范围是合乎逻辑的选择?

【问题讨论】:

    标签: python c++ multithreading macros c-preprocessor


    【解决方案1】:

    预处理器将宏 Py_BEGIN_ALLOW_THREADS 扩展为创建名为 _save 的本地对象的代码。

    预处理器将宏 Py_END_ALLOW_THREADS 扩展为使用 _save 执行线程清理任务的代码。

    如果你把Py_BEGIN_ALLOW_THREADS放在else块里面,Py_END_ALLOW_THREADS创建的代码看不到本地的_save对象,所以你会得到一个错误信息。

    关于相关主题,我建议将Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS 放在第一个执行的位置,那么第二个也执行。如果您有数组类型或优先级队列类型,您的第二个版本的函数将不会为 Py_END_ALLOW_THREADS 执行线程清理任务。

    试试这个:

    uint8_t SerialBuffer::push_msg() {
    
    #if defined (UBUNTU)
      Py_BEGIN_ALLOW_THREADS
    #endif
      uint8_t response = FAIL;
    
      if (_type == ARRAY) {
          // array access
          response = SUCCESS;
      } else if (_type == PRIORITY_QUEUE) {
          // queue access
          response = SUCCESS;
      }
    
    #if defined (UBUNTU)
      Py_END_ALLOW_THREADS
    #endif
    
      return response;
    }
    

    在这个版本中,默认响应是 FAIL,所以你甚至不需要最后的 else 部分。其他 if 语句仅在一切顺利时将响应设置为 SUCCESS。

    【讨论】:

    • 您的意思是指令吗? Py_BEGIN_ALLOW_THREADS 不在 if 语句中。我应该认为#if 指令是有效的,因为预处理器将在所有有效情况下看到相同的宏定义。
    • @errolflynn 谢谢。我将答案更正为“else 块”而不是“if 语句”。
    • 你的风格不错。但是,在其他情况下,我想提早返回以防止运行过多的代码。你知道为什么我的例子不起作用吗?
    • Py_BEGIN_ALLOW_THREADS 不在 else 块中。它在方法范围内。变量_save 应该在整个方法中都是可见的。
    • @errolflynn 哎呀!我显然误读了原始代码sn-p!我有个主意。将预处理后的源代码发送到一个文件,然后检查文件中预处理器在该函数中放置的内容。您可以使用此命令行来执行此操作。 g++ -E file.cpp > file.pp
    【解决方案2】:

    来自[Python.Docs]: Initialization, Finalization, and Threads - Py_BEGIN_ALLOW_THREADS强调是我的):

    此宏扩展为 { PyThreadState *_save; _save = PyEval_SaveThread();。请注意,它包含一个左大括号; 必须与以下Py_END_ALLOW_THREADS 宏匹配。有关此宏的进一步讨论,请参见上文。

    所以,编译错误的答案很清楚:

    • 预处理后,第二个ndPy_END_ALLOW_THREADS 产生无效代码(#if defined (UBUNTU) 中的封闭是无关紧要的,因为当 UBUNTU em> 已定义,未定义时始终有效):

      • 引用(不存在)“_save
      • 一个额外的右大括号 ("}")

    上面的页面也举例说明了这 2 个宏的常见用例:

    PyThreadState *_save;
    _save = PyEval_SaveThread();
    
    ... Do some blocking I/O operation ...
    
    PyEval_RestoreThread(_save);
    

    为什么要这样设计(包括范围界定)像你一样使用时可能会失败,因为这可能会导致难以发现错误(你的示例非常简单,但在一个更复杂的代码片段中,有许多分支需要同样多的 Py_END_ALLOW_THREADS,想象一下错过一个或两次调用它意味着什么)。

    为了解决您的问题,您必须将代码重新设计为:

    • 不直接返回失败,而是标记它,在最后返回(在一个地方),并在返回之前放置 Py_END_ALLOW_THREADS
    • if 分支(ARRAYPRIORITY_QUEUE、...)的每个(或在需要时)内部调用宏对李>
    • 使用(可怕的)转到

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-28
      • 1970-01-01
      • 1970-01-01
      • 2014-01-07
      相关资源
      最近更新 更多