C ++ getter / setter, mutex, точная блокировка - PullRequest
2 голосов
/ 22 февраля 2012

У меня есть объект, который используется несколькими потоками, я хочу заблокировать отдельные переменные-члены, не блокируя весь объект, чтобы разные потоки могли одновременно обращаться к разным переменным-членам. После прочтения некоторых статей я пишу код с использованием функций shared_mutex и getter () / setter ().

    class Test
    {
    public:
    **// variable, shared_mutex and getter/setter for x**
    double x;
    boost::shared_mutex x_mutex;
    double x_getter();
    void x_setter();
    **// variable, shared_mutex and getter/setter for y**
    ......
    **// variable, shared_mutex and getter/setter for z**
    ......
    };

    double Test::x_getter()
    {
      // get shared access
      boost::shared_lock lock(_access);
      return x;
    }

    void Test::x_setter()
    {
      // get exclusive access
      boost::unique_lock lock(_access);
      // do something with x;
    }

    //getter/setter functions for y and z. 
    ......

Код выглядит неуклюже, особенно, когда количество переменных-членов увеличивается. Мне интересно, есть ли лучшие решения для этого типа проблемы.

Спасибо.

Ответы [ 3 ]

7 голосов
/ 22 февраля 2012

Поскольку блокировка, очевидно, нужна только на короткое время фактического чтения / записи данных, вы можете просто инкапсулировать ее с контролируемыми данными в тип, который затем будете использовать в качестве переменных-членов:

// note: you probably should add constructors as well
template<typename T> struct synchronized
{
public:
  synchronized& operator=(T const& newval)
  {
    boost::unique_lock lock(mutex);
    value = newval;
  }
  operator T() const
  {
    boost::unique_lock lock(mutex);
    return value;
  }
private:
  T value;
  boost::shared_mutex mutex;
};

class Test
{
public:
  synchronized<double> x;
  synchronized<int> y;
  synchronized<std::string> z;
};

void foo(Test& t)
{
  double read = t.x; // locked, via synchronized<double>::operator double() const
  t.x = 3.14;        // locked, via synchronized<double>::operator=
}
1 голос
/ 22 февраля 2012

Вы правы, что этот метод выглядит довольно неуклюжим и быстро становится неуправляемым. Поэтому я пытаюсь смоделировать многопоточные проблемы, преодолев зависимость от данных. Однако без дальнейшего контекста к тому, что вы пытаетесь решить, я не могу посоветовать, как смоделировать проблему.

Если вы уже вложили средства в такую ​​архитектуру и уже слишком поздно что-либо менять, я бы подумал об этом.

template<class T>
class SharedValiable
{
    private:
        T                    myT;
        boost::shared_mutex  myTMutex;

    public:
        //
        // Implement appropriate copy, assign and default 
        // to ensure proper value semantics
        //

        T getter() const
        {
            boost::shared_lock lock(_access);
            return x;
        }

        void setter()
        {
            boost::unique_lock lock(_access);
        }
}

Это позволяет защитить каждую переменную так, как вы изначально предполагали, но упрощает добавление новых или удаление членов из классов. Кроме того, шаблон может быть специализирован для определенного типа, который может использовать атомарные операции ОС, такие как int. e.g.:

template<int>
class SharedValiable
{
    private:
        T                    myT;

    public:
        //
        // Implement appropriate copy, assign and default 
        // to ensure proper value semantics
        //

        T getter() const
        {
            // no need to lock, updates are atomic
            return x;
        }

        void setter()
        {
            // no mutex needed we will use an atomic OS op to update
            InterlockedCompareAndExchange(myT, newVal);
        }
}
0 голосов
/ 22 февраля 2012

В общем, я бы советовал против этого.Обычно вы пытаетесь сделать с классом обеспечение какой-либо гарантии состояния объекта при входе и выходе из метода.Например, в объекте списка вам нужно, чтобы ссылки были в согласованном состоянии, вам нужно, чтобы счетчик был правильным и т. Д. Простая блокировка отдельных переменных редко позволяет этому случиться.Если вы изменяете ссылку, не блокируя состояние счетчика, вы врете одно или другое, когда он запрашивается другим потоком в середине обновления.Хуже того, вы можете поменять две ссылки одновременно и в итоге получить список ошибок, который больше не будет работать, в зависимости от вашего хранилища.Конечно, это один из примеров, возможно, но вы должны понять.

Если вы хотите, чтобы несколько потоков могли видеть состояние вашего объекта, тогда вам нужна блокировка чтения / записи всего объекта, который позволил бы такому количеству читателей, как вы хотите, но не позволил бы им видеть объект в середине обновления.

...