Практична операція порівняння та заміни (у кількох словах)


10

У статті з такою ж назвою, що й у цьому питанні, автори описують, як побудувати неблокуючу лінійну операцію з декількома словами CAS, використовуючи лише односкладне CAS. Вони спочатку запроваджують операцію подвійного порівняння-один-своп - RDCSS, наступним чином:

word_t RDCSS(RDCSSDescriptor_t *d) {
  do {
    r = CAS1(d->a2, d->o2, d);
    if (IsDescriptor(r)) Complete(r);
  } while (IsDescriptor(r));
  if (r == d->o2) Complete(d); // !!
  return r;
}

void Complete(RDCSSDescriptor_t *d) {
  v = *(d->a1);
  if (v == d->o1) CAS1(d->a2, d, d->n2);
  else CAS1(d->a2, d, d->o2);
}

де RDCSSDescriptor_tє структура з такими полями:

  • a1 - адреса першої умови
  • o1 - значення, очікуване за першою адресою
  • a2 - адреса другої умови
  • o2 - значення, очікуване за другою адресою
  • n2 - нове значення, яке потрібно записати на другу адресу

Цей дескриптор створюється та ініціалізується один раз у потоці, який ініціює операцію RDCSS - жоден інший потік не має посилання на нього, поки перший CAS1 у функції не стане RDCSSуспішним, роблячи дескриптор доступним (або активним у термінології статті).

Ідея алгоритму полягає в наступному - замініть друге місце пам'яті на дескриптор, який говорить, що ви хочете зробити. Потім, враховуючи, що дескриптор присутній, перевірте місце першого пам'яті, щоб побачити, чи змінилося його значення. Якщо цього немає, замініть дескриптор у другому місці пам'яті на нове значення. В іншому випадку поверніть друге місце пам'яті до старого значення.

Автори не пояснюють, чому рядок із !!коментарем необхідний у статті. Мені здається, що CAS1вказівки у Completeфункції завжди будуть провалюватися після цієї перевірки, за умови, що не буде одночасних змін. І якщо відбулася паралельна модифікація між чеком і CAS в Complete, то потік, який здійснює перевірку, все-таки повинен провалюватися зі своїм CAS в Complete, оскільки паралельна модифікація не повинна використовувати той самий дескриптор d.

Моє питання: Чи можна перевірити в функції RDCSSS, if (r == d->o2)...буде опущений, з RDCSS зберігаючи семантику подвійного порівняння, однієї команди обміну , яка лінеарізуема і безблокіровочний ? (рядок із !!коментарем)

Якщо ні, чи можете ви описати сценарій, коли цей рядок насправді необхідний для забезпечення коректності?

Дякую.


По-перше, щоб зрозуміти, що відбувається, нам потрібно було б побачити структуру даних RDCSSDescriptor_t. По-друге, це, мабуть, тут поза темою, оскільки воно не стосується теоретичних інформатик; було б краще запитати це на сайті stackoverflow.com.
Дейв Кларк

Посилання на папір розірвано.
Аарон Стерлінг

1
Прошу вибачення за посилання - воно має зараз працювати. Я оновив питання, щоб описати, що таке дескриптор. Тому я не розміщував цього повідомлення на сайті stackoverflow.com в тому, що FAQ задає, що цей веб-сайт призначений для питань рівня інформатики на рівні досліджень. Я вважав, що питання замкнутості та лінійності алгоритму кваліфікуються як такі. Сподіваюсь, я неправильно зрозумів FAQ.
axel22

Ключове слово, яке ви пропустили у FAQ, було "теоретичним". Оскільки деякі люди вважають це питання цікавим, я залишаю його відкритим.
Дейв Кларк

3
@Dave: Я не є експертом у цій під-області, але для мене це звучить як дуже типове питання TCS. Вам надаються дві моделі обчислення (A: з однословним CAS, B: з багатословним CAS) та мірою складності (кількість CAS), і вас запитують, чи можете ви моделювати модель B в моделі A, і з найгіршими випадками. (Тут може бути трохи оманом, що моделювання подається як фрагмент коду С замість псевдокоду; це може підказати теоретичній особі, що це пов'язано з проблемами програмування в реальному світі.)
Юкка Суомела,

Відповіді:


9

У паралельних умовах виконання прості речі можуть здаватися дивними ... сподіваюся, що це може допомогти.

У нас Вбудований АТОМНИЙ CAS1 має такий смисловий характер:

int CAS1(int *addr, int oldval, int newval) {
  int currval = *addr;
  if (currval == oldval) *addr = newval;
  return currval;
}

Нам потрібно визначити функцію ATOMIC RDCSS за допомогою CAS1, що має таку семантичну ознаку :

int RDCSS(int *addr1, int oldval1, int *addr2, int oldval2, int newval2) {
  int res = *addr;
  if (res == oldval2 && *addr1 == oldval1) *addr2 = newval2;
  return res;
}

Інтуїтивно: нам потрібно ТОЧНО змінити значення в addr2, лише якщо * addr1 == oldval1 ... якщо інший потік змінюється, ми можемо допомогти іншому потоку завершити операцію, то ми можемо повторити спробу.

Функція RDCSS буде використана (див. Статтю) для визначення CASN. Тепер ми визначаємо дескриптор RDCSS наступним чином:

RDCSSDESCRI
int *addr1   
int oldval1
int *addr2   
int oldval2
int newval2

Тоді ми реалізуємо RDCSS наступним чином:

int RDCSS( RDCSSDESCRI *d ) {
  do {
    res = CAS1(d->addr2, d->oldval2, d);  // STEP1
    if (IsDescriptor(res)) Complete(res); // STEP2
  } while (IsDescriptor(res);             // STEP3
  if (res == d->oldval2) Complete(d);     // STEP4
  return res;
}

void Complete( RDCSSDESCRI *d ) {
  int val = *(d->addr1);
  if (val == d->oldval1) CAS1(d->addr2, d, d->newval2);
    else CAS1(d->addr2, d, d->oldval2);  
}
  • КРОК1: спочатку ми намагаємось змінити значення * addr2 на наш (власний) дескриптор d, якщо CAS1 вдався, то res == d-> oldval2 (тобто res НЕ є дескриптором)
  • STEP2: перевірте, чи res є дескриптором, тобто STEP1 не вдалося (інший потік змінено addr2) ... допоможіть іншому потоку завершити операцію
  • КРОК3: повторіть STEP1, якщо нам не вдалося зберегти наш дескриптор d
  • КРОК 4: якщо ми отримали очікуване значення з addr2, тоді нам вдалося зберегти наш дескриптор (покажчик) в addr2, і ми можемо виконати наше завдання, зберігаючи newval2 в * addr2 iif * addr1 == oldval1

ВІДПОВІДЬ НА ВАШЕ ЗАПИТАННЯ

Якщо ми опустимо STEP4, тоді if (... && * addr1 == oldval1) * addr2 = newval2 частина семантики RDCSS ніколи не буде виконаний (... а ще краще: це може бути виконано бездоганним способом іншими потоками, допомагаючи поточний).

Як ви вказали у вашому коментарі, умова, якщо (res == d1-> oldval2) на STEP4 є непотрібним: навіть якщо ми його опустимо, обидва CAS1 у повному () не зможуть, оскільки * (d-> addr2)! = D . Єдиною його метою є уникати виклику функції.

Приклад T1 = нитка1, T2 = нитка2:

remember that addr1 / addr2 are in a shared data zone !!!

T1 enter RDCSS function
T2 enter RDCSS function
T2 complete STEP1 (and store the pointer to its descriptor d2 in addr2)
T1 at STEP1 the CAS1 fails and res = d2
T2 or T1 completes *(d2->addr2)=d2->newval2 (suppose that *(d2->addr1)==d2->oldval1)
T1 execute STEP1 and now CAS1 can fail because *addr2 == d2->newval2
   and maybe d2->newval2 != d1->oldval2, in every case at the end 
   res == d2->newval2 (fail) or
   res == d1->oldval2 (success)
T1 at STEP2 skips the call to Complete() (because now res is not a descriptor)
T1 at STEP3 exits the loop (because now res is not a descriptor)
T1 at STEP4 T1 is ready to store d1->newval2 to addr2, but only if
   *(d1->addr2)==d (we are working on our descriptor) and *(d1->addr1)==d1->oldval1
   ( Custom() function)

Дякую, гарне пояснення. Я повністю пропустив те, що CAS1 повертає старе значення, а не нове.
axel22

Але в сценарії останні два рядки говорять про те, що: без умови на STEP4, T1 може зберігати значення, оскільки addr2містить d2->newval2. Але, мені здається, що CAS1 у Completeзаповіті просто зазнає невдачі, оскільки він очікує, що старе значення стане дескриптором d1- Т1 нічого не напише. Правильно?
axel22

@ axel22: Я пропустив CAS1 у повному () :-D. Так, ви маєте рацію ... мій приклад помиляється, якщо умова використовується лише для уникнення виклику функції, якщо ми відкинемо if () нічого не зміниться. Очевидно, повне (d) на STEP4 є необхідним. Тепер я модифікую приклад.
Marzio De Biasi

Наскільки я знаю, уникнення CAS не вдасться - це техніка оптимізації кешу, наскільки я знаю, оскільки на реальному апаратному забезпеченні це зазвичай має негативні наслідки, такі як промивання ліній кеш-пам'яті та отримання ексклюзивного доступу до лінії кешу. Я думаю, автор статті хотів, щоб алгоритм був максимально практичним, а також був правильним.
Тім Сегейн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.