Синхронизация доступа к возвращаемому значению
Рассмотрим следующую функцию-член С++:
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 работает!