EPICS Home

Experimental Physics and Industrial Control System


 
1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  <20172018  2019  2020  2021  2022  2023  2024  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  <20172018  2019  2020  2021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: Re: C++ multi threaded application.
From: "Giacomo S." <[email protected]>
To: Andrew Johnson <[email protected]>, [email protected]
Cc: Lucio Zambon <[email protected]>
Date: Mon, 4 Sep 2017 11:08:51 +0200
On 09/01/2017 06:31 PM, Andrew Johnson wrote:
> Hi Giacomo,
>
> Welcome to the EPICS community!

Hello Andrew, thanks for Your kind and thorough reply.

>
> On 09/01/2017 09:59 AM, Giacomo S. wrote:
>> I am new to Epics, and I am writing a C++ multi threaded library in
>> order to write GUIs in Qt and I have the following constraints:
> Are you aware that there are already a number of EPICS projects using
> Qt? You might want to take a look at them since they have already
> debugged the kinds of issues you may be faced with. The main one you
> should start with is epicsQt, https://github.com/qtepics or
> https://qtepics.github.io/index.html

I wasn't aware of the specific project, but I had read about epics and
Qt integration.
In this first step, I don't have to deal with Qt, just a plain C++ library.

>> - I want to monitor a pv in a separate thread;
>>
>> - when a new value is available, I want a callback to be invoked (always
>> in the same background thread). There I extract data and forward it to
>> the main (GUI) thread.
>>
>> - the background thread implements a wait condition so that it sleeps
>> until a new event arrives (for example, exit event or new variable to be
>> monitored is added)
> You should be aware that the Channel Access client library creates a
> background thread for each separate IOC it connects to, and it calls
> your callback routines from those threads. You have no control over
> that, if you talk to PVs on 2 IOCs you will get callbacks from 2
> threads. 

That's a relevant information. Thus, monitoring 100 PVs means at least
100 threads in the application.

> The only difference that enabling preemptive callbacks has on
> that is to allow callbacks to be run as soon as they are ready to be
> run. For thread-sensitive systems like Qt you need to plan for that.

That's why enabling preemptive callbacks made things work as desired
without blocking within ca_pend_event()
Good.

>
>> I set things up like this in the init() method of a class I called
>> "CuMonitorActivity".
>>
>> I used a pv structure as in camonitor C example:
>>
>>
>> void CuMonitorActivity::init()
>> {
> <snip>
>> }
>>
>> NOTE: I enabled preemptive_callback so that I get
>> connection_handler_cb() called regularly when a new event arrives
> You didn't show the routine that actually creates the channel and passes
> in the connection_handler_cb() routine, but a CA *connection handler* is
> only called when the connection state changes, i.e. when the program
> first connects to the PV, and then again later if/when the IOC gets
> started or stopped (or the network is unplugged, etc); that callback
> doesn't see any data from the PV. If you want to see value changes from
> the PV you have to create a subscription to the channel, and give that a
> different callback routine.

Ok. Here's the snippet of code for the monitor setup:


void CuMonitorActivity::init()
{
   
    CuData tk = getToken();

    int nPvs = 1;
    pv* pvs;                    /* Array of PV structures */
    int result = ca_context_create(ca_enable_preemptive_callback);
    if (result != ECA_NORMAL) {
       // deal with error
    }
    else
    {
        pvs = (pv *) calloc (nPvs, sizeof(pv));
       
            /* Connect channels */
            memset(pvs[0].name, 0, 256);
            strncpy(pvs[0].name, tk["src"].toString().c_str(), 255);

            pvs[0].monitor_activity = this; // this is needed for my
internal stuff

            /* Create CA connections */
            int returncode = create_pvs(pvs, nPvs, connection_handler_cb);

            if ( returncode ) {
            // error
        }

    }
}

//
// taken from camonitor's "tool_lib.cpp"
// essentially calls  ca_create_channel()
//
int create_pvs (pv* pvs, int nPvs, caCh *pCB)
{
    int n;
    int result;
    int returncode = 0;

    if (!tsInitC)                /* Initialize start timestamp */
    {
        epicsTimeGetCurrent(&tsStart);
        tsInitC = 1;
    }
    /* Issue channel connections */
    for (n = 0; n < nPvs; n++) {
        result = ca_create_channel (pvs[n].name,
                                    pCB,
                                    &pvs[n],
                                    caPriority,
                                    &pvs[n].ch_id);
        if (result != ECA_NORMAL) {
            fprintf(stderr, "CA error %s occurred while trying "
                            "to create channel '%s'.\n",
ca_message(result), pvs[n].name);
            pvs[n].status = result;
            returncode = 1;
        }
    }

    return returncode;
}

Then, when connection_handler_cb is invoked:

void CuMonitorActivity::connection_handler_cb ( struct
connection_handler_args args )
{
    // Extract the reference to this object
    //
    pv *ppv = ( pv * ) ca_puser ( args.chid );
    CuMonitorActivity* cu_mona = ppv->monitor_activity;
 
    // call the connection_handler of this object
    cu_mona->connection_handler(args);
}

Inside the connection_handler() i call
ca_create_subscription()


// Code is taken from camonitor's.
//
void CuMonitorActivity::connection_handler(connection_handler_args args)
{

    int nConn = 0;

    /* the following three could be overridden from the cmd line in ca
monitor.c */
    int floatAsString = 0;
    unsigned long eventMask = DBE_VALUE | DBE_ALARM;
    int reqElems = 1;

    pv *ppv = ( pv * ) ca_puser ( args.chid );

    if ( args.op == CA_OP_CONN_UP ) {
        nConn++;
        if (!ppv->onceConnected) {
            ppv->onceConnected = 1;
            /* Set up pv structure */
            /* ------------------- */

            /* Get natural type and array count */
            ppv->dbfType = ca_field_type(ppv->ch_id);
            ppv->dbrType = dbf_type_to_DBR_TIME(ppv->dbfType); /* Use
native type */
            if (dbr_type_is_ENUM(ppv->dbrType))                /* Enums
honour -n option */
            {
                if (enumAsNr) ppv->dbrType = DBR_TIME_INT;
                else          ppv->dbrType = DBR_TIME_STRING;
            }
            else if (floatAsString &&
                     (dbr_type_is_FLOAT(ppv->dbrType) ||
dbr_type_is_DOUBLE(ppv->dbrType)))
            {
                ppv->dbrType = DBR_TIME_STRING;
            }
            /* Set request count */
            ppv->nElems   = ca_element_count(ppv->ch_id);
            ppv->reqElems = 1;

            /* Issue CA request */
            /* ---------------- */
            /* install monitor once with first connect */
            ppv->status = ca_create_subscription(ppv->dbrType,
                                                 ppv->reqElems,
                                                 ppv->ch_id,
                                                 eventMask,
                                                
CuMonitorActivity::event_handler_cb,
                                                 (void*)ppv,
                                                 NULL);
        }
    }
    else if ( args.op == CA_OP_CONN_DOWN ) {
        nConn--;
        ppv->status = ECA_DISCONN;
        print_time_val_sts(ppv, reqElems);
    }
}


>
>> From within connection_handler_cb I extract the current
>> CuMonitorActivity instance (stored in pv's monitor_activity field) and I
>> invoke the object's extraction method. Data is posted in the main thread.
>>
>> NOTE: I notice that connection_handler_cb is invoked from ANOTHER
>> thread, not my main thread, not my secondary thread. This should be as
>> expected I guess for things to work and given the ca_
>> enable_preemptive_callback option.
> Right, see my remarks about threading above. Note that you are allowed
> to make calls to other CA routines from within your callbacks, and in
> fact that is the recommended way to set up your subscriptions. However
> you should be careful to only subscribe for data from a PV only once; if
> the IOC gets restarted your connection handler will be called again on
> reconnect, and unless you're careful that code might create a second
> subscription to the same PV, which will result in your event callback
> being run twice or more for each data update from the IOC.
>
>> * when I post an "exitEvent" on my secondary thread from the main one
>> (e.g. the GUI is closed),  "ca_context_destroy();" is invoked in my
>> secondary thread (not the Epics thread that calls connection_handler_cb
>> during monitoring - is this OK? )
> Yes, as long as the thread was attached to the CA context. You should
> attach all threads that you want to make CA calls from to your original
> context using ca_current_context() and ca_attach_context().
>
>> * Is there a way to ADD more PVs to monitor WHILE others are being
>> monitored (for example, is it legit to call create_pvs() again ) ?
> Yes, although you didn't show us your create_pvs() routine. You can add
> and remove channels and subscriptions whenever you like; about the only
> thing you can't call from within a callback routine is ca_pend_event().

OK, now it should be clear how I create the pvs.
Could you tell me how to add and remove channels and subscriptions?

As to threading, all channel/monitor setup is made up in a secondary thread.
The idea is thus to add and remove channels within that very secondary
thread.
So, as you stated, the callbacks would be invoked from the N different
threads on the event callback within the
CuMonitorActivity object.
Data, from within the event callback, is then queued and posted on the
main thread.
I have now to check whether I need to lock guard the code within the
event callback or not.

Nonetheless, if my observations are correct, that makes a good starting
point.

Thanks for Your help.

Giacomo, Elettra, Trieste, Italy


>
>> * Are there any observations/corrections to my approach and to my
>> statements above?
> Go check out epicsQt before you spend more time reinventing the wheel.
> There is a separate mailing list for EPICS users programming with Qt and
> you might want to subscribe to that:
>     http://www.aps.anl.gov/epics/qti-talk/index.php
>
> - Andrew
>


Replies:
Re: C++ multi threaded application. Michael Davidsaver
References:
C++ multi threaded application. Giacomo S.
Re: C++ multi threaded application. Andrew Johnson

Navigate by Date:
Prev: Re: Base 3.15 and record initialization Johnson, Andrew N.
Next: Re: Archiver: Problems with disconnected PVs Gabriel de Souza Fedel
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  <20172018  2019  2020  2021  2022  2023  2024 
Navigate by Thread:
Prev: Re: C++ multi threaded application. Andrew Johnson
Next: Re: C++ multi threaded application. Michael Davidsaver
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  <20172018  2019  2020  2021  2022  2023  2024