【BehaviorTree.CPP行为树学习:】并发和异步节点Concurrency and Asynchrous Nodes

Understand Asynchrous Nodes, Concurrency and Parallelism

【了解异步节点,并发性和并行性】

When designing reactive Behavior Trees, it is important to understand 2 main concepts:

  • what we mean by "Asynchronous" Actions VS "Synchronous" ones.
  • The difference between Concurrency and Parallelism in general and in the context of BT.CPP.

【在设计构思行为树时,理解下面两个主要概念很重要:

我们所说的“异步”操作与“同步”操作的含义。

一般情况下以及BT.CPP上下文中并发性和并行性之间的差异。】

原英文链接:Concurrency and Asynchronous Nodes - BehaviorTree.CPP

Concurrency vs Parallelism

【并行与并行】

If you Google those words, you will read many good articles about this topic.

Defintions

Concurrency is when two or more tasks can start, run, and complete in overlapping time periods. It doesn't necessarily mean they'll ever both be running at the same instant.

Parallelism is when tasks literally run at the same time in different threads, e.g., on a multicore processor.

【如果你用谷歌搜索这些单词,你会读到很多关于这个主题的好文章。

定义

并发是指两个或多个任务可以在相同的时间段内启动、运行和完成。这并不一定意味着它们会在同一时刻同时运行。

并行性是指任务在不同的线程中同时运行,例如在多核处理器上。(强调同一时刻同时运行)】

BT.CPP executes all the nodes Concurrently, in other words:

  • The Tree execution engine itself is single-threaded.
  • all the tick() methods are always executed sequentially.
  • if any tick() method is blocking, the entire execution is blocked.

【BT.CPP并行执行所有节点,换句话说:

树执行引擎本身是单线程的。

所有tick()方法始终按顺序执行。

如果任何tick()方法被阻塞,则整个执行都会被阻塞。

We achieve reactive behaviors through "concurrency" and asynchronous execution.

In other words, an Action that takes a long time to execute should, instead, return as soon as possible the state RUNNING to notify that the action was started, and only when ticked again check if the action is completed or not.

An Asynchronous node may delegate this long execution either to another process, another server or simply another thread.

【我们实现行为树之间的反应(各个节点的执行)是通过“并发”和异步执行来的。

换言之,执行时间较长的Action应该尽快返回状态RUNNING,以通知该操作已启动,只有再次勾选时(tick),再检查该操作是否已完成。

异步节点可以将这个长时间执行交给给另一个进程、另一个服务器或只是另一个线程去实现。

Asynchronous vs Synchronous

【异步与同步】

In general, an Asynchronous Action (or TreeNode) is simply one that:

【通常,异步操作(或TreeNode)只是一种:】

  • May return RUNNING instead of SUCCESS or FAILURE, when ticked.

【勾选后,可能返回RUNNING而不是SUCCESS或FAILURE】

  • Can be stopped as fast as possible when the method halt() (to be implemented by the developer) is invoked.

【可以在调用方法halt()(由开发人员实现)时尽快停止。】

When your Tree ends up executing an Asynchronous action that returns running, that RUNNING state is usually propagated backbard and the entire Tree is itself in the RUNNING state.

【当您的树最终执行一个返回running的异步操作时,该running状态通常会被传播到backbard,并且整个树本身也处于running状态。】

In the example below, "ActionE" is asynchronous and RUNNING; when a node is RUNNING, usually its parent returns RUNNING too.

【在下面的示例中,“ActionE”是异步和RUNNING;当节点为RUNNING时,其父节点通常也返回RUNNING。】

 

Let's consider a simple "SleepNode". A good template to get started is the StatefulAction

【让我们想一个简单的“SleepNode”。StatefulAction是一个很好的入门模板】

// Example of Asynchronous node that use StatefulActionNode as base class
class SleepNode : public BT::StatefulActionNode
{
  public:
    SleepNode(const std::string& name, const BT::NodeConfiguration& config)
      : BT::StatefulActionNode(name, config)
    {}

    static BT::PortsList providedPorts()
    {
      // amount of milliseconds that we want to sleep
      return{ BT::InputPort<int>("msec") };
    }

    NodeStatus onStart() override
    {
      int msec = 0;
      getInput("msec", msec);

      if( msec <= 0 ) {
        // No need to go into the RUNNING state
        return NodeStatus::SUCCESS;
      }
      else {
        using namespace std::chrono;
        // once the deadline is reached, we will return SUCCESS.
        deadline_ = system_clock::now() + milliseconds(msec);
        return NodeStatus::RUNNING;
      }
    }

    /// method invoked by an action in the RUNNING state.
    NodeStatus onRunning() override
    {
      if ( std::chrono::system_clock::now() >= deadline_ ) {
        return NodeStatus::SUCCESS;
      }
      else {
        return NodeStatus::RUNNING;
      }
    }

    void onHalted() override
    {
      // nothing to do here...
      std::cout << "SleepNode interrupted" << std::endl;
    }

  private:
    std::chrono::system_clock::time_point deadline_;
};

In the code above:

  1. When the SleepNode is ticked the first time, the onStart() method is executed. This may return SUCCESS immediately if the sleep time is 0 or will return RUNNING otherwise.
  2. We should continue ticking the tree in a loop. This will invoke the method onRunning() that may return RUNNING again or, eventually, SUCCESS.
  3. Another node might trigger a halt() signal. In this case, the onHalted() method is invoked. We can take the opportunity to clean up our internal state.

【在上述代码中:

1、第一次勾选SleepNode时,将执行onStart()方法。如果睡眠时间为0,则可能立即返回SUCCESS,否则将返回RUNNING。

2、我们应该继续在循环中勾选树。这将调用onRunning()方法,该方法可能再次返回RUNNING,或者最终返回SUCCESS。

3、另一个节点可能会触发一个halt()信号。在这种情况下,将调用onHalted()方法。我们可以借此机会清理我们的内部状态。】

Avoid blocking the execution of the tree

【避免阻塞树的执行】

A wrong way to implement the SleepNode would be this one:

【实现SleepNode的错误方法是:】

// This is the synchronous version of the Node. probably not what we want.
class BadSleepNode : public BT::ActionNodeBase
{
  public:
    BadSleepNode(const std::string& name, const BT::NodeConfiguration& config)
      : BT::ActionNodeBase(name, config)
    {}

    static BT::PortsList providedPorts()
    {
      return{ BT::InputPort<int>("msec") };
    }

    NodeStatus tick() override
    {  
      int msec = 0;
      getInput("msec", msec);
      // This blocking function will FREEZE the entire tree :(
      std::this_thread::sleep_for( std::chrono::milliseconds(msec) );
      return NodeStatus::SUCCESS;
     }

    void halt() override
    {
      // No one can invoke this method, because I freezed the tree.
      // Even if this method COULD be executed, there is no way I can
      // interrupt std::this_thread::sleep_for()
    }
};

The problem with multi-threading

【多线程的问题】

In the early days of this library (version 1.x), spawning a new thread looked like a good solution to build asynchronous Actions.

【在这个库(1.x版)的早期,生成一个新线程看起来是构建异步Actions的一个很好的解决方案。】

That was a bad idea, for multiple reasons:

【这是个不好的方法,原因有很多:】

  • Accessing the blackboard in a thread-safe way is harder (more about this later).
  • You probably don't need to.
  • People think that this will magically make the Action "asynchronous", but they forget that it is still their responsibility to stop that thread "somehow" when the halt()method is invoked.

【以线程安全的方式访问黑板比较困难(稍后将详细介绍)。

你可能不需要。

人们认为这会神奇地使Action“异步”,但他们忘记了,在调用halt()方法时,他们仍有责任“以某种方式”停止该线程。】

For this reason, users are usually discouraged from using 

BT::AsyncActionNode as a base class. Let's have a look again at the SleepNode.

【因此,通常不鼓励用户使用BT::AsyncActionNode作为基类。让我们再看看SleepNode。】

// This will spawn its own thread. But it still has problems when halted
class BadSleepNode : public BT::AsyncActionNode
{
  public:
    BadSleepNode(const std::string& name, const BT::NodeConfiguration& config)
      : BT::ActionNodeBase(name, config)
    {}

    static BT::PortsList providedPorts()
    {
      return{ BT::InputPort<int>("msec") };
    }

    NodeStatus tick() override
    {  
      // This code runs in its own thread, therefore the Tree is still running.
      // This seems good but the thread still can't be aborted
      int msec = 0;
      getInput("msec", msec);
      std::this_thread::sleep_for( std::chrono::milliseconds(msec) );
      return NodeStatus::SUCCESS;
    }

    // The halt() method can not kill the spawned thread :(

    // Keep in mind that most of the time we should not
    // override AsyncActionNode::halt()
};

A correct version would be:

【正确的做法是:】

// I will create my own thread here, for no good reason
class ThreadedSleepNode : public BT::AsyncActionNode
{
  public:
    ThreadedSleepNode(const std::string& name, const BT::NodeConfiguration& config)
      : BT::ActionNodeBase(name, config)
    {}

    static BT::PortsList providedPorts()
    {
      return{ BT::InputPort<int>("msec") };
    }

    NodeStatus tick() override
    {  
      // This code run in its own thread, therefore the Tree is still running.
      int msec = 0;
      getInput("msec", msec);

      using namespace std::chrono;
      const auto deadline = system_clock::now() + milliseconds(msec);

      // periodically check isHaltRequested() 
      // and sleep for a small amount of time only (1 millisecond)
      while( !isHaltRequested() && system_clock::now() < deadline )
      {
        std::this_thread::sleep_for( std::chrono::milliseconds(1) );
      }
      return NodeStatus::SUCCESS;
    }

    // The halt() method will set isHaltRequested() to true 
    // and stop the while loop in the spawned thread.
};

As you can see, this looks more complicated than the version we implemented first, using 

BT::StatefulActionNode. This pattern can still be useful in some case, but you must remember that introducing multi-threading make things more complicated and should be avoided by default.

【如您所见,这看起来比我们第一次使用BT::StatefulActionNode实现的版本更复杂。这种模式在某些情况下仍然有用,但您必须记住,引入多线程会使事情变得更复杂,在默认情况下应该避免使用多线程在行为树里。】

Advanced example: client / server communication

【高级示例:客户端/服务器通信】

Frequently, people using BT.CPP execute the actual task in a different process.

【通常,使用BT.CPP的人会在不同的流程中执行实际任务。】

A typical (and recommended) way to do this in ROS is using ActionLib.

【在ROS中实现客户端和服务端通信的典型(也是推荐的)方法是使用ActionLib。(为什么部不是service呢?因为service只会返回最终结果,而actionlib可以返回中间过程和最终结果,具有实时反馈功能,在某些情况下我们需要实时反馈信息,比如我们在下载一个比较大的文件时,我们期望能够得到实时反馈的下载进度信息等)】

ActionLib provides exactly the kind of API that we need to implement correctly an asynchronous behavior:

【ActionLib提供了正确实现异步行为所需的API:】

  1. A non-blocking function to start the Action.
  2. A way to monitor the current state of execution of the Action.
  3. A way to retrieve the result or the error messages.
  4. The ability to preempt / abort an action that is being executed.

【1、启动Action的非阻塞函数。

2、监视操作执行的当前状态的方法。

3、检索结果或错误消息的方法。

4、抢占/中止正在执行的操作的能力。】

None of these operations are "blocking", therefore we don't need to spawn our own thread.

【这些操作都不是“阻塞”的,因此我们不需要生成自己的线程。】

More generally, let's assume that the developer has their own inter-processing communication, with a client/server relationship between the BT executor and the actual service provider.

【更一般地说,让我们假设开发人员有自己的交互处理通信,BT执行者和实际服务提供者之间的关系是 客户机/服务器关系。】

The corresponding pseudo-code implementation will look like this:

【相应的伪代码实现如下所示:】

// This action talk to a remote server
class ActionClientNode : public BT::StatefulActionNode
{
  public:
    SleepNode(const std::string& name, const BT::NodeConfiguration& config)
      : BT::StatefulActionNode(name, config)
    {}

    NodeStatus onStart() override
    {
      // send a request to the server
      bool accepted = sendStartRequestToServer();
      // check if the request was rejected by the server
      if( !accepted ) {
        return NodeStatus::FAILURE;
      }
      else {
        return NodeStatus::RUNNING;
      }
    }

    /// method invoked by an action in the RUNNING state.
    NodeStatus onRunning() override
    {
      // more psuedo-code
      auto current_state = getCurrentStateFromServer();

      if( current_state == DONE )
      {
        // retrieve the result
        auto result = getResult();
        // check if this result is "good"
        if( IsValidResult(result) ) {
          return NodeStatus::SUCCESS;
        } 
        else {
          return NodeStatus::FAILURE;
        }
      }
      else if( current_state == ABORTED ) {
        // fail if the action was aborted by some other client
        // or by the server itself
        return NodeStatus::FAILURE;
      }
      else {
        // probably (current_state == EXECUTING) ?
        return NodeStatus::RUNNING;
      }
    }

    void onHalted() override
    {
      // notify the server that the operation have been aborted
      sendAbortSignalToServer();
    }
};

【代码实现功能,类似与ros actionlib 客户端,根据服务端返回的状态来判断最终结果是成功失败还是运行中】

【说明】

BehaviorTree.CPP行为树学习系列是翻译自源英文网站,由于个人知识能力水平有限,如有错误的地方,请在评论区留言,会不断修改和完善的

 

 

 

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值