Menu

Using a Thread-safe shell

this feature was introduced in Dezyne release 2.0.0.

A Dezyne Thread-safe Shell guarantees safe use of a Dezyne system component in a multi-threaded environment. It also enables the use of the 'blocking' keyword.

Syntax

Use the dzn command-line client to generate code and a thread-safe shell:

dzn code -l c++ -s SYSTEM FILE    <1>(Since 2.0.0)

Explanation:

1 Generates code for the SYSTEM which encapsulates the components within from external threads.

Semantics

A thread-safe shell wraps a Dezyne system component. In addition to an instance of the Dezyne component it contains a thread and an event queue. External code can call event functions on system ports. The thread-safe shell defers each external call by posting a function object in the event queue. A thread private to the thread-safe shell takes deferred functions from the queue and executes them one by one. Thus, external calls are serviced in the order of arrival.

An external call of a provided port in event blocks until the thread-safe shell private thread has completed the deferred function call. The external call blocks until a reply statement has been executed for the input event port. A subsequent call on a blocked port will block until the prior call returns.

An external call of a required port out event returns a soon as the event call is scheduled. The external call return is not synchronized with the actual execution of the event by a thread-safe shell private thread.

Example

Generating C++ code with a thread-safe shell for component SYS results in files: SYS.hh, SYS.cc, BHV.hh and IA.hh.

A call of SYS::pp.in.iv() captures input parameters by value to prevent data races. The call schedules a call to SYS::bhv.pp.in.iv() and blocks the calling thread until the scheduled call returns.

A call of SYS::rp.out.o() captures input parameters by value to prevent data races. The call schedules a call to SYS::bhv.rp.out.o() and returns immediately.

component SYS {
  provides IA pp;
  requires IA rp;

  system {
    BHV bhv;
    pp <=> bhv.pp;
    bhv.rp <=> rp;
  }
}

component BHV {
  provides IA pp;
  requires IA rp;
}

extern int $int$;
interface IA {
  in void iv(int i);
  out void o(int i);

  behaviour {
    on iv: {}
    on optional: o;
  }
}

File SYS.hh:

#ifndef SYS_HH
#define SYS_HH

#include

#include
#include
#include

#include "BHV.hh"

#include "IA.hh"
#include "IA.hh"

namespace dzn {struct locator;}

struct SYS
{
  dzn::meta dzn_meta;
  dzn::runtime dzn_runtime;

  dzn::locator dzn_locator;
  BHV bhv;

  IA pp;
  IA rp;

  dzn::pump dzn_pump;
  SYS(const dzn::locator&);
  void check_bindings() const;
  void dump_tree(std::ostream& os=std::clog) const;
};

#endif // SYS_HH

File SYS.cc:

#include "SYS.hh"

SYS::SYS(const dzn::locator& locator)
: dzn_meta{"","SYS",0,{&bhv.dzn_meta},{}}
, dzn_locator(locator.clone().set(dzn_runtime).set(dzn_pump))
, bhv(dzn_locator)
, pp(bhv.pp)
, rp(bhv.rp)
, dzn_pump()
{
  pp.in.iv = [&] (int i) {
    return dzn::shell(dzn_pump, [&,i] {return bhv.pp.in.iv(i);});
  };
  rp.out.o = [&] (int i) {
    return dzn_pump([&,i] {return bhv.rp.out.o(i);});
  };
  bhv.pp.out.o = std::ref(pp.out.o);
  bhv.rp.in.iv = std::ref(rp.in.iv);

  bhv.dzn_meta.parent = &dzn_meta;
  bhv.dzn_meta.name = "bhv";
}

void SYS::check_bindings() const
{
  dzn::check_bindings(&dzn_meta);
}

void SYS::dump_tree(std::ostream& os) const
{
  dzn::dump_tree(os, &dzn_meta);
}
Enjoy this article? Don't forget to share.