การดำเนินการเปรียบเทียบและสลับหลายคำในทางปฏิบัติ


10

ในบทความที่มีชื่อเดียวกันกับคำถามนี้ผู้เขียนอธิบายถึงวิธีการสร้างการดำเนินการCAS แบบหลายคำที่ไม่บล็อก เชิงเส้น แบบปรับขนาดได้โดยใช้ CAS แบบคำเดียว พวกเขาแนะนำการดำเนินการแบบ double-compare-single-swap ครั้งแรก - 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 - ค่าใหม่ที่จะเขียนตามที่อยู่ที่สอง

descriptor นี้ถูกสร้างและเริ่มต้นครั้งเดียวในเธรดที่เริ่มต้นการดำเนินการ RDCSS - ไม่มีเธรดอื่นใดที่มีการอ้างอิงถึงจนกระทั่ง CAS1 แรกในฟังก์ชันRDCSSสำเร็จ, ทำให้ descriptor สามารถเข้าถึงได้ (หรือใช้งานในคำศัพท์ของกระดาษ)

แนวคิดเบื้องหลังอัลกอริทึมมีดังต่อไปนี้ - แทนที่ตำแหน่งหน่วยความจำที่สองด้วย descriptor ซึ่งบอกว่าคุณต้องการทำอะไร จากนั้นระบุว่า descriptor มีอยู่ให้ตรวจสอบตำแหน่งหน่วยความจำแรกเพื่อดูว่ามีการเปลี่ยนแปลงค่าหรือไม่ หากไม่มีให้แทนที่ descriptor ที่ตำแหน่งหน่วยความจำที่สองด้วยค่าใหม่ มิฉะนั้นให้ตั้งค่าตำแหน่งหน่วยความจำที่สองกลับเป็นค่าเดิม

ผู้เขียนไม่ได้อธิบายว่าเหตุใดจึง!!จำเป็นต้องใช้บรรทัดที่มีความคิดเห็นในบทความ ฉันดูเหมือนว่าCAS1คำแนะนำในการCompleteทำงานจะล้มเหลวเสมอหลังจากการตรวจสอบนี้โดยที่ไม่มีการแก้ไขพร้อมกัน และหากมีการเปลี่ยนแปลงเกิดขึ้นพร้อมกันระหว่างการตรวจสอบและ CAS ในCompleteแล้วด้ายทำการตรวจสอบจะยังคงล้มเหลวด้วย CAS ในตั้งแต่การปรับเปลี่ยนพร้อมกันไม่ควรใช้อธิบายเดียวกันCompleted

คำถามของฉันคือสามารถตรวจสอบในฟังก์ชั่นRDCSSS, if (r == d->o2)...ถูกมองข้ามด้วย RDCSS ที่ยังคงรักษาความหมายของเปรียบเทียบคำแนะนำและแลกเปลี่ยนคู่เดียวซึ่งเป็นlinearizableและล็อคฟรี ? (เรียงตาม!!ความคิดเห็น)

ถ้าไม่คุณสามารถอธิบายสถานการณ์ที่บรรทัดนี้จำเป็นจริง ๆ เพื่อความถูกต้องหรือไม่

ขอบคุณ.


ประการแรกเพื่อให้เข้าใจว่าเกิดอะไรขึ้นเราต้องดูโครงสร้างข้อมูล RDCSSDescriptor_t ประการที่สองนี่อาจเป็นหัวข้อที่นี่เนื่องจากไม่เกี่ยวข้องกับวิทยาศาสตร์คอมพิวเตอร์เชิงทฤษฎี มันจะดีกว่าถ้าถามใน stackoverflow.com
Dave Clarke

ลิงก์ไปยังกระดาษเสีย
Aaron Sterling

1
ฉันขอโทษสำหรับลิงค์ - มันควรจะทำงาน ฉันได้อัปเดตคำถามเพื่ออธิบายว่า descriptor คืออะไร เหตุผลที่ฉันไม่ได้โพสต์สิ่งนี้บน stackoverflow.com คือคำถามที่พบบ่อยบอกว่าเว็บไซต์นี้มีไว้สำหรับคำถามระดับการวิจัยในสาขาวิทยาศาสตร์คอมพิวเตอร์ ฉันคิดว่าคำถามเกี่ยวกับความอิสระล็อคและความสามารถเชิงเส้นตรงของอัลกอริทึมมีคุณสมบัติเช่นนี้ ฉันหวังว่าฉันเข้าใจคำถามที่พบบ่อยไม่ถูกต้อง
axel22

คำสำคัญที่คุณพลาดในคำถามที่พบบ่อยคือ "เชิงทฤษฎี" ในขณะที่บางคนพบคำถามที่น่าสนใจฉันจะเปิดทิ้งไว้
Dave Clarke

3
@Dave: ฉันไม่ได้เป็นผู้เชี่ยวชาญในพื้นที่ย่อยนี้ แต่สำหรับฉันนี่ดูเหมือนคำถาม TCS ทั่วๆไป คุณจะได้รับการคำนวณสองแบบ (A: ด้วยคำเดียว CAS, B: ด้วยคำหลายคำ) และการวัดความซับซ้อน (จำนวน CAS) และคุณจะถูกถามว่าคุณสามารถจำลองแบบ B ในแบบ A หรือไม่ และด้วยค่าใช้จ่ายที่เลวร้ายที่สุด (นี่อาจเป็นความเข้าใจผิดเล็กน้อยที่การจำลองนั้นได้รับเป็นชิ้นส่วนของรหัส C แทนที่จะเป็น pseudocode ซึ่งอาจแนะนำให้บุคคลทฤษฎีที่เกี่ยวข้องกับความท้าทายการเขียนโปรแกรมในโลกแห่งความเป็นจริง)
Jukka Suomela

คำตอบ:


9

ในสภาพแวดล้อมรันไทม์พร้อมกันสิ่งที่เรียบง่ายอาจดูแปลก ... หวังว่าสิ่งนี้จะช่วยได้

เรามีCASIL-IN ATOMIC 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 Descriptorด้วยวิธีต่อไปนี้:

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 เป็น descriptor (ของเราเอง) d ถ้า CAS1 สำเร็จแล้ว res == d-> oldval2 (เช่น res ไม่ใช่ descriptor)
  • ขั้นตอนที่ 2: ตรวจสอบว่า res เป็น descriptor นั่นคือขั้นตอนที่ 1 ล้มเหลว (เธรดอื่นเปลี่ยน addr2) ... ช่วยให้เธรดอื่นดำเนินการให้เสร็จสิ้น
  • ขั้นตอนที่ 3: ลอง STEP1 อีกครั้งหากเราไม่สามารถเก็บ descriptor ของเราได้สำเร็จ
  • ขั้นตอนที่ 4: ถ้าเราดึงค่าที่คาดไว้จาก addr2 เราก็ประสบความสำเร็จในการจัดเก็บ descriptor (ตัวชี้) ใน addr2 และเราสามารถดำเนินการจัดเก็บ newval2 ถึง * addr2 iif * addr1 == oldval1

ตอบคำถามของคุณ

ถ้าเราเว้น STEP4 ดังนั้นif (... && * addr1 == oldval1) * addr2 = newval2ส่วนหนึ่งของความหมายของ RDCSS จะไม่ถูกดำเนินการ (... หรือดีกว่า: สามารถดำเนินการในรูปแบบที่คาดเดาไม่ได้ ปัจจุบัน)

ตามที่คุณระบุไว้ในความคิดเห็นของคุณว่าเงื่อนไข(res == d1-> oldval2)ที่ STEP4 นั้นไม่จำเป็น: แม้ว่าเราจะละเว้นก็ตามทั้ง CAS1 ใน Complete () จะล้มเหลวเพราะ * (d-> addr2)! = d . วัตถุประสงค์เพียงอย่างเดียวของมันคือหลีกเลี่ยงการเรียกใช้ฟังก์ชัน

ตัวอย่าง T1 = thread1, T2 = thread2:

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

แต่ในสถานการณ์ที่ผ่านมา 2 สายบอกว่า: โดยไม่มีเงื่อนไขที่ Step4 ที่ T1 สามารถเก็บค่าเพราะมีaddr2 d2->newval2แต่ฉันคิดว่า CAS1 ในCompleteพินัยกรรมจะล้มเหลวเพราะคาดว่าคุณค่าเดิมจะเป็นตัวบ่งชี้d1- T1 จะไม่เขียนอะไรเลย ขวา?
axel22

@ axel22: ฉันพลาด CAS1 ใน Complete () :-D ใช่คุณพูดถูก ... ตัวอย่างของฉันผิดเงื่อนไข if ถูกใช้เพียงเพื่อหลีกเลี่ยงการเรียกใช้ฟังก์ชันถ้าเราทิ้งif ()ไม่มีอะไรเปลี่ยนแปลง เห็นได้ชัดว่าจำเป็นต้องใช้Complete (d)ที่ STEP4 ตอนนี้ฉันปรับเปลี่ยนตัวอย่าง
Marzio De Biasi

การหลีกเลี่ยง CAS เราคาดหวังว่าจะล้มเหลวเป็นเทคนิคการเพิ่มประสิทธิภาพแคชเท่าที่ฉันทราบเนื่องจากในฮาร์ดแวร์จริงมักจะมีผลกระทบเชิงลบเช่นการล้างข้อมูลแคชบรรทัดและการเข้าถึงการเข้าถึงแคชแบบเอกสิทธิ์เฉพาะบุคคล ฉันเดาว่าผู้เขียนบทความต้องการให้อัลกอริทึมเป็นประโยชน์มากที่สุดนอกเหนือจากการแก้ไขให้ถูกต้อง
Tim Seguine
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.