Представьте себе код, который освобождает общий указатель:
auto tmp = &(the_ptr->a);
*tmp = 10;
the_ptr.dec_ref();
Если dec_ref () не имеет семантики «выпуска», для компилятора (или ЦП) вполне нормально перемещать вещи из перед dec_ref () в после него (например):
auto tmp = &(the_ptr->a);
the_ptr.dec_ref();
*tmp = 10;
И это небезопасно, так как dec_ref () также может быть вызван из другого потока одновременно и удалить объект. Таким образом, он должен иметь семантику «выпуска», чтобы вещи оставались там до dec_ref ().
Давайте теперь представим, что деструктор объекта выглядит так:
~object() {
auto xxx = a;
printf("%i\n", xxx);
}
Также мы немного модифицируем пример и получим 2 потока:
// thread 1
auto tmp = &(the_ptr->a);
*tmp = 10;
the_ptr.dec_ref();
// thread 2
the_ptr.dec_ref();
Тогда «агрегированный» код будет выглядеть так:
// thread 1
auto tmp = &(the_ptr->a);
*tmp = 10;
{ // the_ptr.dec_ref();
if (0 == atomic_sub(...)) {
{ //~object()
auto xxx = a;
printf("%i\n", xxx);
}
}
}
// thread 2
{ // the_ptr.dec_ref();
if (0 == atomic_sub(...)) {
{ //~object()
auto xxx = a;
printf("%i\n", xxx);
}
}
}
Однако, если у нас есть только семантика выпуска для atomic_sub (), этот код можно оптимизировать таким образом:
// thread 2
auto xxx = the_ptr->a; // "auto xxx = a;" from destructor moved here
{ // the_ptr.dec_ref();
if (0 == atomic_sub(...)) {
{ //~object()
printf("%i\n", xxx);
}
}
}
Но в этом случае деструктор не всегда будет печатать последнее значение «a» (этот код больше не является свободным от гонки). Вот почему нам также нужна семантика получения для atomic_sub (или, строго говоря, нам нужен барьер получения, когда счетчик становится 0 после декремента).
person
wonder.mice
schedule
10.02.2015