Guarded objects

Guarded objects encapsulate data shared by several active objects or tasks. They do not own their own threads but can synchronize calls from various threads.

Operations that are protected are called guarded operations. A guarded operation is considered critical enough to need to enforce mutual exclusion. A guarded object is an object that owns at least one guarded operation.

One way to implement a guarded object is to give it a mutex so every operation that is explicitly set to be guarded locks the mutex at the beginning of the operation and releases it at the end.

An RiCMonitor member is added to the structures of guarded objects. For example:

typedef struct A A;
struct A {
    RiCMonitor ric_monitor;
    /* other members of A */
};

RiCMonitor is a monitor type defined in the IBM® Rational® Rhapsody® framework.

The ric_monitor subobject is used only for operations of this object that are tagged as guarded. You can tag an operation as guarded using the CG::Operation::Concurrency property.

The guarded operation is protected inside a wrapper operation, which is responsible for the protection. The guarded operation is generated as a private operation as follows:

For example, two functions are generated for a guarded operation increase() of an object B:

The declaration for the wrapper operation is generated in the specification file for the object:

int B_increase(B* const me, int i);

The wrapper operation, increase(), obtains a lock on the ric_monitor object, calls the guarded operation, and finally releases the lock:

int B_increase(B* const me, int i) {
    int wrapper_return_value;
    RIC_OPERATION_LOCK(&me->ric_monitor);
    wrapper_return_value = B_increase_guarded(me, i);
    RIC_OPERATION_FREE(&me->ric_monitor);
    return wrapper_return_value;
}

Once the wrapper function obtains a lock, the guarded operation is protected and can perform its critical operations without being accessed by another object until the lock is freed:

static int B_increase_guarded(B* const me, int i) {
    {
        /*#[ operation increase(int) */
        return i++;
        /*#]*/
    }
}

Similarly, the cleanup operation for guarded objects is generated into a wrapper operation and a guarded operation that performs the cleanup. For example, the cleanup for a guarded object B first locks B, then calls cleanup_guarded(), which does the actual cleanup:

void B_Cleanup(B* const me) {
    RIC_OPERATION_LOCK(&me->ric_monitor);
    B_Cleanup_guarded(me);
}
void B_Cleanup_guarded(B* const me) {
    RiCMonitor_cleanup(&me->ric_monitor);
}

You can also use the lock and free macros directly to avoid the extra support needed for wrapper operations.


Feedback