开发者

C++/Boost: Synchronize access to a resource across multiple method (getter) calls

开发者 https://www.devze.com 2023-03-17 04:31 出处:网络
I\'m not sure if this is a question regarding programming technique or design but I\'m open for suggestions.

I'm not sure if this is a question regarding programming technique or design but I'm open for suggestions.

The problem: I want to create an abstraction layer between data sources (sensors) and consumers. The idea is that the consumers only "know" the interfaces (abstract base class) of different sensor types. Each of this sensor types usually consists of several individual values which all have their own getter methods.

As an example I will use a simplified GPS sensor.

class IGpsSensor {

    public:
        virtual float getLongitude() = 0;
        virtual float getLatitude() = 0;
        virtual float getElevation() = 0;

        // Deviations
        virtual float getLongitudeDev() = 0;
        virtual float getLatitudeDev() = 0;
        virtual float getElevationDev() = 0;

        virtual int getNumOfSatellites() = 0;
};

Since updates to the sensor are done by a different thread (details are up to the implementation of the interface), synchronizing getters and also the update methods seems like a reasonable approach to ensure consistency.

So far so good. In most cases this level of synchronization should suffice. However, sometimes it might be necessary to aquire more than one value (with consecutive getXXX() calls) and ensure that no update is happening in between. Whether this is necessary or not (and which values are important) is up to the consumer.

Sticking to the example, in a lot of cases it is only important to know longitude and latitude (but hopefully both relating to the same update()). I admit that this could be done be grouping them together into a "Position" class or struct. But a consumer might also use the sensor for a more complicated algorithm and requires the deviation as well.

Now I was wondering, what would be a proper way to do this.

Solutions I could think of:

  • Group all possible values into a struct (or class) and add an additional (synchronized) getter returning copies of all values at once - seems like a lot of unnecessary overhead to me in case only 2 or 3 out of maybe 10 values are needed.

  • Add a method returning a reference to the mutex used within the data source to allow locking by the consumer - this doesn't feel like "good design". And since getters are already synchronized, using a recursive mutex 开发者_JAVA百科is mandatory. However, I assume that there are multiple readers but only one writer and thus I'd rather go with a shared mutex here.

Thanks for your help.


How about exposing a "Reader" interface? To get the reader object, you would do something like this:

const IGpsSensorReader& gps_reader = gps_sensor.getReader();

The IGpsSensorReader class could have access to protected members of the IGpsSensor class. When constructed, it would acquire the lock. Upon destruction, it would release the lock. An accessor could do something like this:

{ //block that accesses attributes
   const IGpsSensorReader& gps_reader = gps_sensor.getReader();
   //read whatever values from gps_reader it needs
} //closing the scope will destruct gps_reader, causing an unlock

You could also expose a getWriter method to the thread doing the updates. Internally, you could use boost's shared_mutex to mediate access between the readers and the writers.


A technique I've used in some simple projects is to only provide access to a proxy object. This proxy object holds a lock for the duration of its lifetime, and provides the actual interface to my data. This access does no synchronization itself, because it is only available through the proxy which is already locked appropriately. I've never tried expanding this to a full scale project, but it has seemed to work well for my purposes.


Possible solution: derive all your source classes from

class Transaction {
  pthread_mutex_t mtx;
  // constructor/destructor
public:
  void beginTransaction() { pthread_mutex_lock(&mtx); } // ERROR CHECKING MISSING
  void endTransaction() { pthread_mutex_unlock(&mtx); } // DO ERROR CHECKING
protected:
  // helper method
  int getSingle(int *ptr)
  { int v; beginTransaction(); v=*ptr; endTransaction(); return v; }
};

If you need to read out multiple values, use begin/endTransaction methods. To define your getValue functions, just call getSingle with pointer to the appropriate member [this is just a convenience method so that you don't have to call begin/endTransaction in each getValue function.].

You will need to flesh out some details, because if your getValue functions use begin/endTransaction, you won't be able to call them inside a transaction. (A mutex can be locked only once, unless it is configured to be recursive.)

0

精彩评论

暂无评论...
验证码 换一张
取 消