Представете си код, който освобождава споделен указател:
auto tmp = &(the_ptr->a);
*tmp = 10;
the_ptr.dec_ref();
Ако dec_ref() няма семантика за "освобождаване", е напълно добре за компилатор (или CPU) да премести нещата от преди 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