正确的方法是打开并检查仅更改一次的运行时标志

时间:2017-06-20 16:54:01

标签: c multithreading performance optimization

在C中(特别是在使用微控制器时),我遇到了一些情况,我开始一个等待设置初始标志的线程。为了这个例子,让我们说我有一个线程只有在收到一些特定的数据包后才能真正启动。 一旦说出旗帜,就永远不会被取消!我处理的方式是这样的:

int receive_flag = 0;
void thread() 
{
  while (1)
  {
     usleep(SOME_CONSTANT_DELAY);
     if (!receive_flag)
       continue;
     else
       // this code should always run after some init happens
  }
}

然后可能在我的通用数据包接收器(不同的源文件)中,我有类似

的东西
void process_packet(const byte p[])
{
  // imagine that I have checks to see if
  // packet is valid (size > 0, certain format, blabla)
  if (p[0] == 0xFF) // maybe the first instance of this header
  // will trigger the above thread to actually do stuff
    settheflag();  // imagine I have a settheflag API exposed that changes
    // receive_flag to 1
  else
    // do other stuff
}

因为我的问题是关于样式/优化,我故意使代码不完整(也许我应该做了伪代码,但是哦),但我希望你们能得到这个想法。假设它周围的其余代码都是正确的,我不在乎线程是否开始了几次迭代(所以我不关心任何异步问题)。

这是解决这种情况的正确方法吗?我担心的是:

  • 在循环线程中,if(!receive_flag)是否会引起额外的性能问题? ESP。因为在设置一次后,receive_flag永远不应为0。
  • 在数据包处理程序中,最好在设置标志之前检查标志是否已设置,或者只是始终设置标志(这是关于性能,我知道两者都可以工作......这是一个问题,它是否更好?写一次并始终在OR之后读或总是写)

如果有一种更好的方法可以解决这个问题,那也值得赞赏!

这也可能与操作系统有关......如果是这样,我现在关注的是eLinux。如果它需要更具体(使用处理器和东西),我们只能说这是最新Raspian OS上的Raspberry pi 2A:900MHz四核ARM Cortex-A7 CPU。如果您愿意,您可以自由使用其他架构,但请尽量将其保留在嵌入式系统的上下文中。

我也会感谢C ++(直到C ++ 11)解决方案,但我更喜欢C解决方案。

编辑: 为了澄清,我无法从process_packet函数启动线程以进行代码组织。

2 个答案:

答案 0 :(得分:2)

你有两个相关的问题:结构(你问过的)和一致性(你没有,但仍然需要解决)。

<强>结构

以相反的优先顺序排列(最后是最好的)因为它只是以这种方式运作得更好™:

  • 更好:重构你的循环,这样你就不会继续检查那些无法改变的东西:

    void thread() {
      // wait until we're ready
      while (!receive_flag) {
         usleep(SOME_CONSTANT_DELAY);
      }
      // init steady state here
      // then enter steady state loop
      while (1) {
         // your code here
      }
    }
    
  • 更好的是:使用正确的同步而不是忙碌等待

    void thread() {
      // wait until we're ready
      {
         std::unique_lock<std::mutex> lock(flag_mutex);
         while (!receive_flag) {
             flag_condvar.wait(lock);
         }
      }
      // init steady state here ...
    
  • 更好:如果你有“...... settheflag API公开......”,你可以添加一个匹配的waitfortheflag()函数em>一次在线程函数的开头。只需将初始等待循环移动到那里。

  • 最好:在准备运行之前不要启动线程。

    理想情况下,在现有的settheflag API函数中移动线程。

    可能有充分的理由避免从数据包处理程序启动线程,例如。如果它很贵,但这是最可行的方式。

一致性:您对该标志使用普通int是不可移植的!

可能在你的平台上工作(我不知道,所以会遵从你),但是不同的编译器(或者你当前编译器的未来更新)可以合法地提升{ {1}}离开循环并且只执行一次,这将非常显着地破坏所有内容。

解决方案按优先顺序排列(首先是反射对称性):

  1. 正确同步(receive_flag等或C {/ 1}}
  2. 使您的旗帜成为std::mutex(仅限C ++,但完全正确且便携)
  3. 依赖于特定于平台的内容(实际上是文档记录的)(编译器内在函数,例如GCC的pthread_mutex_*
  4. 制作你的旗帜std::atomic并依靠你的平台做你希望的事情,而不仅仅是语言规范要求(这实际上是一个编译器扩展,明确地记录了期望的行为)
  5. 依靠你现有的平台(正在继续做,永远做正确的事)

答案 1 :(得分:1)

为什么不使用std :: condition_variable或pthread_cond_t,而不是在一段时间内休眠并检查变量。这些是条件变量,您可以阅读here。您可以使用它们在程序中的任何位置等待和等待线程。这是上面代码的示例,但使用条件变量而不是bools。

select xmlserialize(content xmlquery('
  copy $i := $p modify (
    for $j in $i/RSET/ROW/SDC_TO[text()="UK0"]
      return replace value of node $j with "UK1"
    )
    return $i'
  passing xmltype('<?xml version="1.0"?>
<RSET>
 <ROW>
  <SDC_FNAME>JQ13868001.XML</SDC_FNAME>
  <SDC_RECORD>6</SDC_RECORD>
  <SDC_FROMDT>06/14/2017 08:13:58</SDC_FROMDT>
  <SDC_TODT>06/16/2017 08:13:58</SDC_TODT>
  <SDC_TNAME>S_STYLE</SDC_TNAME>
  <SDC_FROM>AB</SDC_FROM>
  <SDC_TO>UK0</SDC_TO>
 </ROW>
 <ROW>
  <SDC_FNAME>JQ13868002.XML</SDC_FNAME>
  <SDC_RECORD>6</SDC_RECORD>
  <SDC_FROMDT>06/14/2017 08:13:58</SDC_FROMDT>
  <SDC_TODT>06/16/2017 08:13:58</SDC_TODT>
  <SDC_TNAME>S_STYLE</SDC_TNAME>
  <SDC_FROM>AB</SDC_FROM>
  <SDC_TO>UK1</SDC_TO>
 </ROW>
</RSET>') AS "p"
  returning content) indent) as result
from dual;

RESULT                                                                          
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<RSET>
  <ROW>
    <SDC_FNAME>JQ13868001.XML</SDC_FNAME>
    <SDC_RECORD>6</SDC_RECORD>
    <SDC_FROMDT>06/14/2017 08:13:58</SDC_FROMDT>
    <SDC_TODT>06/16/2017 08:13:58</SDC_TODT>
    <SDC_TNAME>S_STYLE</SDC_TNAME>
    <SDC_FROM>AB</SDC_FROM>
    <SDC_TO>UK1</SDC_TO>
  </ROW>
  <ROW>
    <SDC_FNAME>JQ13868002.XML</SDC_FNAME>
    <SDC_RECORD>6</SDC_RECORD>
    <SDC_FROMDT>06/14/2017 08:13:58</SDC_FROMDT>
    <SDC_TODT>06/16/2017 08:13:58</SDC_TODT>
    <SDC_TNAME>S_STYLE</SDC_TNAME>
    <SDC_FROM>AB</SDC_FROM>
    <SDC_TO>UK1</SDC_TO>
  </ROW>
</RSET>