Personal tools
You are here: Home HowTos CPP: A Tango device class with its own thread
Document Actions

CPP: A Tango device class with its own thread

by etaurel last modified 2007-02-02 09:32

This HowTo details how you can write a Tango class when it has to have its own thread and with data shared between the device attributes and the thread

It is relatively common to have Tango classes where you permanently have something to do. This could be implemented by running a separate thread which will do these tasks. If you also have attributes with data shared between the thread and the attributes themselves, special care has to be taken to protect the data access on both side. This HowTo gives one possible solution to this problem.

Let's suppose we have a Tango class called ThExample with two scalar read_write attributes called FirstAttr (DevLong) and SecondAttr (DevDouble). Both of these attributes used data shared with the thread.


Here is the data added to the ThExample class

...Pogo generated code...
protected :
// Add your own data members here
//-----------------------------------------

public:
typedef struct ShData
{
double db_data;
long lo_data;
bool th_exit;
};

protected:

TheThread *th;
omni_mutex the_mutex;
ShData the_shared_data;
ShData the_local_data;
};

To store the effectively shared data, a structure called ShData has been defined with the two shared data and a boolean used to ask the thread to suicide itself. Four data members have been added to the class:

  1. A pointer to the thread object
  2. A mutex which will be used to protect the shared data access
  3. The shared data themselves
  4. A local copy of the shared data
Then, it is needed to define the thread class

class TheThread: public omni_thread, public Tango::LogAdapter
{
public:
TheThread(ThExample *dev,omni_mutex &mut,ThExample::ShData &dat):
omni_thread(),Tango::LogAdapter(dev),the_mutex(mut),the_shared_data(dat)
{start_undetached();}

virtual ~TheThread() {}

void *run_undetached(void *);

private:
omni_mutex &the_mutex;
ThExample::ShData &the_shared_data;

bool local_th_exit;
};

The class is called TheThread and inherits from
  • the omni_thread class
  • the LogAdapter class to be able to print/send Tango log messages
This class has a ctor which receives as arguments:
  • The device which will be used for the Tango Log Messages
  • A reference to the mutex used to protect the shared data
  • A reference to the shared data structure
It is a omni_thread undetached thread and the class ctor will start the thread executing its run_undetached() method. This class stores the references received as ctor arguments and has a data member to keep a local and private copy of the boolean used by the Tango device to ask it to suicide

The thread code is the following
void *TheThread::run_undetached(void *ptr)
{
DEBUG_STREAM << "The thread starts..." << endl;

//
// Init data
//

{
omni_mutex_lock lo(the_mutex);
the_shared_data.lo_data = 0;
the_shared_data.db_data = 10.0;
}

//
// An endless loop
//

while(1)
{
{
omni_mutex_lock lo(the_mutex);
local_th_exit = the_shared_data.th_exit;
the_shared_data.lo_data++;
the_shared_data.db_data++;
}

if (local_th_exit == true)
{
DEBUG_STREAM << "Arrg, I have been killed" << endl;
omni_thread::exit();
}

sleep(1);
DEBUG_STREAM << "This stupid thread has executed a new loop" << endl;
}
}
When the thread starts, it initialise the shared data and enter an endless loop. Within this loop, it modifies the shared data and  read the device suicide order flag. If the device asks for suicide it does this...If not, it sleeps and re-enter the loop. The access to the shared data is protected using the mutex received as one of the thread instance ctor.

On the device side, the init_device() method creates the thread and initialises the Tango attributes related data
void ThExample::init_device()
{
INFO_STREAM << "ThExample::ThExample() create device " << device_name << endl;

// Initialise variables to default values
//--------------------------------------------


//
// Init attr related data
//

attr_FirstAttr_read = &the_local_data.lo_data;
attr_SecondAttr_read = &the_local_data.db_data;

//
// Create and start the thread
//

the_shared_data.th_exit = false;

th = new TheThread(this,the_mutex,the_shared_data);
}

The method initialises the pointer used during the Tango attribute set_value() method to points the the shared data local copy. Then it initialises the suicide order flag to false and it creates the thread (which will also starts it)

The device delete_device() method kills the thread and is coded like the following
void ThExample::delete_device()
{
// Delete device's allocated object

//
// Ask the thread to suicide itself
//

{
omni_mutex_lock lo(the_mutex);
the_shared_data.th_exit = true;
}

void *ptr;
DEBUG_STREAM << "Waiting for the thread to exit" << endl;
th->join(&ptr);
}
This method asks the thread to exit and then waits for the thread termination in the join() method call. Note that the join() call will also reclaim the thread memory.

The Tango class read_attr_hardware() method is coded like the following
void ThExample::read_attr_hardware(vector<long> &attr_list)
{
DEBUG_STREAM << "ThExample::read_attr_hardware(vector<long> &attr_list) entering... "<< endl;
// Add your own code here

omni_mutex_lock lo(the_mutex);
the_local_data = the_shared_data;
}
This code takes the mutex control and copy the shared data within a local copy. This is necessary because when you use attribute, the data you pass to the attribute object with the set_value are given to Tango by pointer. Then, it is somewhere within the Tango layer that the data will be effectively copied to be sent on the network and you do not have a way to protect this code. Using a local copy is a safe way to return data to the client.

The code of the read and write method for the attributes is
void ThExample::read_FirstAttr(Tango::Attribute &attr)
{
DEBUG_STREAM << "ThExample::read_FirstAttr(Tango::Attribute &attr) entering... "<< endl;
attr.set_value(attr_FirstAttr_read);
}


void ThExample::write_FirstAttr(Tango::WAttribute &attr)
{
DEBUG_STREAM << "ThExample::write_FirstAttr(Tango::WAttribute &attr) entering... "<< endl;

attr.get_write_value(attr_FirstAttr_write);
{
omni_mutex_lock lo(the_mutex);
the_shared_data.lo_data = attr_FirstAttr_write;
}
}
The read_FirstAttr() method is simply a call to the attribute set_value() method with the pointer sets to point to the local data copy
The write_FirstAttr() method retrieves the value sent by the caller and writes it into the shared memory area (protected code)

This HowTo gives one solution of the problem. There are certainly some other that I let you imagine. It creates one thread by device which could not be well adapted to every kind of problems. It is also possible to create and start the thread in the ThExampleClass singleton ctor if you want only one thread whatever the device number is. On top of that, if you have many data shared between the Tango class and the thread, you can defined several mutexes which will allow you to have a finer tuning on the shared data protection.

Powered by Plone, the Open Source Content Management System

This site conforms to the following standards: