Синхронизация доступа к возвращаемому значению

Рассмотрим следующую функцию-член С++:

 size_t size() const
 {
    boost::lock_guard<boost::mutex> lock(m_mutex);
    return m_size;
 }

Цель состоит в том, чтобы не синхронизировать доступ к переменной частного члена m_size, а просто чтобы убедиться, что вызывающий абонент получает действительное значение для m_size. Цель состоит в том, чтобы предотвратить возврат функции m_size в то же время, когда какой-либо другой поток модифицирует m_size.

Но есть ли какие-либо потенциальные условия гонки, связанные с вызовом этой функции? Я не уверен, что блокировка стиля RAII здесь достаточна для защиты от состояния гонки. Предположим, что деструктор блокировки вызывается до того, как возвращаемое значение функции вставляется в стек?

Мне нужно сделать что-то вроде следующего, чтобы гарантировать безопасность потоков?

 size_t size() const
 {
    size_t ret;

    {
      boost::lock_guard<boost::mutex> lock(m_mutex);
      ret = m_size;
    }

    return ret;
 }

Ответы

Ответ 1

Оба ваши конструктора примера будут делать то, что вы ищете. Следующая информация из стандарта поддерживает поведение, которое вы ищете (даже в первом примере):

12.4/10 Деструкторы:

Деструкторы вызываются неявно... для построенного объекта с автоматическим временем хранения (3.7.2), когда вызывается блок, в котором создается объект.

А, предложения 6.6/2 Jump (из которых return один):

При выходе из области действия (однако выполняется) деструкторы (12.4) вызываются для всех построенных объектов с автоматической продолжительностью хранения (3.7.2) (именованные объекты или временные), объявленные в этой области, в обратном порядке их выражение. Передача из цикла, из блока или обратно после инициализированной переменной с автоматическим временем хранения включает в себя уничтожение переменных с автоматической продолжительностью хранения, которые находятся в области в точке, переданной из, но не в точке, переданной в.

Итак, в точке return переменная lock находится в области видимости, поэтому dtor не был вызван. После выполнения return dtor для переменной lock вызывается (таким образом, освобождая блокировку).

Ответ 2

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

В принципе, вы можете думать об этом так: требуется копия возвращаемого значения, иначе деструктор будет вызван, следовательно, может повредить все возвращаемое значение до того, как оно будет возвращено.

Деструктор вызывается после оператора return. Возьмите этот эквивалентный пример:

#include <assert.h>

class A
{
public:
    ~A()
    {
        x = 10;
    }
    int x;
};

int test()
{
    A a;
    a.x = 5;
    return a.x;
}

int main(int argc, char* argv[])
{
    int x = test();
    assert(x == 5);
    return 0;
}

Ответ 3

Исходный код в порядке: деструктор будет вызван после сохранения возвращаемого значения. Это самый принцип RAII работает!