实用的多字比较和交换操作


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

我的问题是:在使用RDCSS仍保持可线性化且无的双重比较,单一交换指令的语义的情况下RDCSSS,是否if (r == d->o2)...可以省略函数中的检查?(与评论一致)!!

如果不是,您是否可以描述为确保正确性实际上必须使用此行的情况?

谢谢。


首先,要了解发生了什么,我们需要查看数据结构RDCSSDescriptor_t。其次,这可能不在这里,因为它不涉及理论计算机科学。最好在stackoverflow.com上问这个问题。
戴夫·克拉克

到纸张的链接已断开。
亚伦·斯特林

1
我为该链接表示歉意-现在应该可以使用了。我已经更新了问题以描述描述符是什么。我之所以没有将其发布在stackoverflow.com上,是因为FAQ指出此站点是针对计算机科学领域的研究级问题。我认为算法的无锁性和线性化问题就足以证明这一点。我希望我对FAQ的理解不正确。
axel22 2011年

您在常见问题解答中错过的关键词是“理论”。当某些人发现这个问题很有趣时,我将其保留为开放状态。
Dave Clarke

3
@Dave:我不是该领域的专家,但是对我来说,这听起来像是一个非常典型的TCS问题。您得到了两种计算模型(A:使用单字CAS,B:使用多字CAS)和复杂性度量(CAS数量),系统会询问您是否可以在模型A中模拟模型B,以及最坏情况的开销。(在这里,将模拟作为一段C代码而不是伪代码给出,可能有点误导;这可能向理论人员暗示,这与现实世界中的编程挑战有关。)
Jukka Suomela 2011年

Answers:


9

在并发运行时环境中,简单的事情看起来很奇怪……希望这可以有所帮助。

我们有一个内置原子CAS1,其语义如下:

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

我们需要定义一个原子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;
}

直观地说:仅当* addr1 == oldval1 ...时,我们才需要同时更改addr2的值。如果另一个线程正在更改它,我们可以帮助另一个线程完成操作,然后可以重试。

将使用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步:如果未能成功存储描述符d,请重试第1步
  • 第4步:如果我们从addr2获取了期望值,那么我们就成功地将描述符(指针)存储在addr2中,并且可以完成将newval2存储到* addr2 iif * addr1 == oldval1的任务

回答您的问题

如果我们省略了STEP4,则将永远不会执行RDCSS语义的if(... && * addr1 == oldval1)* addr2 = newval2部分(...或更好:它可以由其他线程以可预测的方式执行)当前的)。

正如您在评论中所指出的那样,在STEP4上不需要if(res == d1-> oldval2)条件:即使我们忽略它,Complete()中的两个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 2011年

但是,在该场景中,最后两行表示:没有STEP4的条件,T1可以存储值,因为addr2contains d2->newval2。但是,在我看来,CAS1中的CAS1 Complete只会失效,因为它希望旧值成为描述符d1-T1不会写入任何内容。对?
axel22 2011年

@ axel22:我在Complete():-D中错过了CAS1。是的,您是对的……我的示例是错误的,if条件仅用于避免函数调用,如果我们丢弃if()则什么也没有改变。显然,STEP4中的Complete(d)是必需的。现在,我修改示例。
Marzio De Biasi

据我所知,避免我们期望失败的CAS是一种缓存优化技术,因为在实际硬件上,它通常具有负面影响,例如刷新缓存行和获得对缓存行的独占访问权限。我想论文的作者除了希望算法正确之外,还希望算法尽可能实用。
蒂姆·塞吉恩
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.