ฉันจะทำให้การก่อสร้างที่เป็นสากลมีประสิทธิภาพมากขึ้นได้อย่างไร


16

"การก่อสร้างสากล" เป็นคลาส wrapper สำหรับวัตถุตามลำดับที่ทำให้มันเป็นเส้นตรง (เงื่อนไขความมั่นคงที่แข็งแกร่งสำหรับวัตถุที่เกิดขึ้นพร้อมกัน) ตัวอย่างเช่นนี่คือโครงสร้างที่ปราศจากการรอที่ปรับใช้แล้วใน Java จาก [1] ซึ่งทึกทักว่ามีคิวรอที่ปราศจากการรอWFQซึ่งเป็นไปตามSequentialอินเทอร์เฟซ

public interface WFQ<T> // "FIFO" iteration
{
    int enqueue(T t); // returns the sequence number of t
    Iterable<T> iterateUntil(int max); // iterates until sequence max
}
public interface Sequential
{
    // Apply an invocation (method + arguments)
    // and get a response (return value + state)
    Response apply(Invocation i); 
}
public interface Factory<T> { T generate(); } // generate new default object
public interface Universal extends Sequential {}

public class SlowUniversal implements Universal
{
    Factory<? extends Sequential> generator;
    WFQ<Invocation> wfq = new WFQ<Invocation>();
    Universal(Factory<? extends Sequential> g) { generator = g; } 
    public Response apply(Invocation i)
    {
        int max = wfq.enqueue(i);
        Sequential s = generator.generate();
        for(Invocation invoc : wfq.iterateUntil(max))
            s.apply(invoc);
        return s.apply(i);
    }
}

การนำไปใช้งานนี้ไม่เป็นที่น่าพอใจมากนักเนื่องจากมันช้ามาก (คุณจำได้ว่าการเรียกใช้ทุกครั้งและต้องเล่นซ้ำทุกครั้งที่มีการใช้งาน - เรามีรันไทม์เชิงเส้นในขนาดประวัติ) มีวิธีใดบ้างที่เราสามารถขยายWFQและSequentialอินเทอร์เฟซ (ในวิธีที่เหมาะสม) เพื่อให้เราสามารถบันทึกขั้นตอนบางอย่างเมื่อใช้การร้องขอใหม่?

เราสามารถทำให้มีประสิทธิภาพมากขึ้น (ไม่ใช่ขนาดเชิงเส้นของรันไทม์โดยเฉพาะการใช้หน่วยความจำก็ลดลงเช่นกัน) โดยไม่สูญเสียคุณสมบัติที่ปราศจากการรอคอย?

การอธิบาย

"โครงสร้างสากล" เป็นคำที่ฉันค่อนข้างแน่ใจว่าถูกสร้างขึ้นโดย [1] ซึ่งยอมรับวัตถุที่ไม่ปลอดภัย แต่เข้ากันได้กับเธรดซึ่งเป็นลักษณะทั่วไปของSequentialอินเทอร์เฟซ การใช้คิวที่ปราศจากการรอการสร้างครั้งแรกจะนำเสนอวัตถุที่เป็นเส้นตรงซึ่งปลอดภัยและปรับขนาดได้ซึ่งมีลักษณะเป็นเส้นตรงซึ่งปราศจากการรอ (ซึ่งจะถือว่าเป็นการกำหนดและหยุดapplyการดำเนินการ)

นี่เป็นวิธีที่ไม่มีประสิทธิภาพเนื่องจากวิธีการนั้นมีประสิทธิภาพในการให้เธรดโลคัลแต่ละตัวเริ่มต้นจากกระดานชนวนใหม่ทั้งหมดและใช้ทุกการดำเนินการที่เคยบันทึกไว้ ในกรณีใด ๆ งานนี้เพราะมันประสบความสำเร็จในการประสานได้อย่างมีประสิทธิภาพโดยใช้WFQเพื่อตรวจสอบลำดับที่การดำเนินการทั้งหมดควรใช้: โทรด้ายทุกapplyจะเห็นในท้องถิ่นเดียวกันSequentialวัตถุที่มีลำดับเดียวกันของInvocations ใช้กับมัน

คำถามของฉันคือว่าเราสามารถ (เช่น) แนะนำกระบวนการล้างข้อมูลบนพื้นหลังซึ่งอัปเดต "สถานะเริ่มต้น" เพื่อให้เราไม่ต้องรีสตาร์ทตั้งแต่เริ่มต้น สิ่งนี้ไม่ง่ายเหมือนการมีตัวชี้ปรมาณูที่มีตัวชี้เริ่มต้น - วิธีการเหล่านี้สูญเสียการรับประกันที่ปราศจากการรอ ความสงสัยของฉันคือวิธีการตามคิวอื่นอาจทำงานที่นี่

ศัพท์แสง:

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

ตัวอย่างการทำงานตามที่ร้องขอ (ตอนนี้บนหน้าเว็บที่จะไม่หมดอายุ)

[1] และเฮอร์ลิฮี Shavit, ศิลปะของการเขียนโปรแกรมมัลติ


คำถามที่ 1 จะตอบได้ก็ต่อเมื่อเรารู้ว่า "งาน" มีความหมายต่อคุณอย่างไร
Robert Harvey

@RobertHarvey ผมแก้ไขได้ - ทั้งหมดที่จะต้องมี "งาน" เป็นเสื้อคลุมที่จะรอฟรีและการดำเนินงานทั้งหมดในCopyableSequentialที่ถูกต้อง - linearizability Sequentialแล้วควรทำตามจากความจริงที่ว่ามันเป็น
VF1

มีคำที่มีความหมายมากมายในคำถามนี้ แต่ฉันพยายามดิ้นรนเพื่อให้พวกเขาเข้าใจว่าคุณกำลังพยายามทำให้สำเร็จ คุณสามารถให้คำอธิบายเกี่ยวกับปัญหาที่คุณพยายามแก้ไขและทำให้คำศัพท์บางอย่างเล็กน้อยได้หรือไม่?
JimmyJames

@JimmyJames ฉันได้อธิบายไว้ใน "ความคิดเห็นเพิ่มเติม" ภายในคำถาม โปรดแจ้งให้เราทราบหากมีศัพท์แสงอื่นที่ชัดเจน
VF1

ในย่อหน้าแรกของความคิดเห็นที่คุณพูดว่า "เธรดวัตถุที่ไม่ปลอดภัย แต่เข้ากันได้กับเธรด" และ "เวอร์ชันเชิงเส้นของวัตถุ" มันไม่ชัดเจนว่าคุณหมายถึงอะไรเพราะthread-safeและlinearizableนั้นเกี่ยวข้องกับคำสั่งปฏิบัติการเท่านั้น แต่คุณใช้มันเพื่ออธิบายวัตถุซึ่งเป็นข้อมูล ผมเข้าใจว่าการภาวนา (ซึ่งไม่ได้ถูกกำหนด) ได้อย่างมีประสิทธิภาพตัวชี้วิธีการและมันเป็นวิธีการที่ไม่ด้ายปลอดภัย ฉันไม่ทราบความหมายของเธรดที่เข้ากันได้
JimmyJames

คำตอบ:


1

นี่คือคำอธิบายและตัวอย่างของวิธีการที่จะประสบความสำเร็จ แจ้งให้เราทราบหากมีชิ้นส่วนที่ไม่ชัดเจน

สรุปสาระสำคัญด้วยแหล่งที่มา

สากล

การเริ่มต้น:

ดัชนีเธรดถูกนำไปใช้ในแบบที่เพิ่มขึ้นแบบอะตอม นี้จัดการโดยใช้ชื่อAtomicInteger nextIndexดัชนีเหล่านี้ถูกกำหนดให้กับเธรดผ่านThreadLocalอินสแตนซ์ซึ่งเริ่มต้นตัวเองโดยรับดัชนีถัดไปจากnextIndexและเพิ่มขึ้น สิ่งนี้จะเกิดขึ้นในครั้งแรกที่มีการดึงดัชนีของแต่ละเธรดในครั้งแรก A ThreadLocalถูกสร้างขึ้นเพื่อติดตามลำดับสุดท้ายที่กระทู้นี้สร้างขึ้น มันเริ่มต้นได้ 0 การอ้างอิงวัตถุจากโรงงานตามลำดับจะถูกส่งผ่านและเก็บไว้ สองกรณีจะถูกสร้างขึ้นขนาดAtomicReferenceArray nวัตถุหางถูกกำหนดให้กับการอ้างอิงแต่ละรายการซึ่งได้รับการกำหนดค่าเริ่มต้นด้วยสถานะเริ่มต้นจากSequentialโรงงาน nเป็นจำนวนเธรดสูงสุดที่อนุญาต แต่ละองค์ประกอบในอาร์เรย์เหล่านี้ 'อยู่' กับดัชนีเธรดที่สอดคล้องกัน

ใช้วิธี:

นี่คือวิธีการทำงานที่น่าสนใจ มันทำต่อไปนี้:

  • สร้างโหนดใหม่สำหรับการเรียกใช้นี้: mine
  • ตั้งค่าโหนดใหม่นี้ในอาร์เรย์ประกาศที่ดัชนีของเธรดปัจจุบัน

จากนั้นจะเริ่มการวนซ้ำลำดับ มันจะดำเนินต่อไปจนกว่าการร้องขอปัจจุบันจะถูกจัดลำดับ:

  1. ค้นหาโหนดในอาร์เรย์ประกาศโดยใช้ลำดับของโหนดสุดท้ายที่สร้างโดยเธรดนี้ เพิ่มเติมเกี่ยวกับเรื่องนี้ในภายหลัง
  2. หากพบโหนดในขั้นตอนที่ 2 แต่ยังไม่ได้เรียงลำดับให้ดำเนินการต่อไปมิเช่นนั้นก็เพียงเน้นที่การเรียกใช้ปัจจุบัน สิ่งนี้จะพยายามช่วยโหนดอื่น ๆ หนึ่งโหนดต่อการเรียกใช้
  3. ไม่ว่าจะเลือกโหนดใดในขั้นตอนที่ 3 พยายามลำดับต่อไปหลังจากโหนดลำดับสุดท้าย (เธรดอื่นอาจรบกวน) โดยไม่คำนึงถึงความสำเร็จตั้งค่าการอ้างอิงส่วนหัวของเธรดปัจจุบันเป็นลำดับที่ส่งกลับโดย decideNext()

กุญแจสำคัญในการวนซ้ำซ้อนที่อธิบายไว้ข้างต้นเป็นdecideNext()วิธีการ เพื่อให้เข้าใจว่าเราต้องดูคลาสของโหนด

คลาสโหนด

คลาสนี้ระบุโหนดในรายการที่ลิงก์เป็นสองเท่า การกระทำในคลาสนี้มีไม่มากนัก วิธีการส่วนใหญ่เป็นวิธีการค้นคืนแบบง่าย ๆ ซึ่งควรอธิบายได้ด้วยตนเอง

วิธีหาง

สิ่งนี้จะคืนค่าอินสแตนซ์โหนดพิเศษที่มีลำดับ 0 มันจะทำหน้าที่เป็นตัวยึดตำแหน่งจนกว่าการร้องขอจะแทนที่มัน

คุณสมบัติและการเริ่มต้น

  • seq: หมายเลขลำดับ, เริ่มต้นที่ -1 (หมายถึงไม่มีลำดับ)
  • invocation: apply()ค่าของการภาวนาของ ตั้งอยู่บนการก่อสร้าง
  • next: AtomicReferenceสำหรับลิงค์ไปข้างหน้า เมื่อกำหนดแล้วจะไม่มีการเปลี่ยนแปลง
  • previous: AtomicReferenceสำหรับลิงก์ย้อนกลับที่กำหนดตามลำดับและลบโดยtruncate()

ตัดสินใจต่อไป

วิธีนี้เป็นเพียงหนึ่งเดียวในโหนดที่มีตรรกะที่ไม่น่ารำคาญ โดยสรุปแล้วโหนดจะเสนอเป็นตัวเลือกให้เป็นโหนดถัดไปในรายการที่เชื่อมโยง compareAndSet()วิธีการจะตรวจสอบหากมีการอ้างอิงเป็นโมฆะและถ้าเป็นเช่นนั้นการตั้งค่าการอ้างอิงไปยังผู้สมัคร หากการอ้างอิงถูกตั้งค่าไว้แล้วจะไม่ทำอะไรเลย การดำเนินการนี้เป็นแบบอะตอมดังนั้นหากมีผู้สมัครสองคนในเวลาเดียวกันจะมีเพียงคนเดียวเท่านั้นที่เลือก สิ่งนี้รับประกันเพียงหนึ่งโหนดเท่านั้นที่จะถูกเลือกเป็นโหนดถัดไป หากเลือกโหนดตัวเลือกลำดับของมันจะถูกตั้งค่าเป็นค่าถัดไปและเป็นลิงค์ก่อนหน้าของมันถูกตั้งค่าเป็นโหนดนี้

การกระโดดกลับไปที่คลาส Universal ใช้วิธีการ ...

การเรียกdecideNext()ใช้โหนดลำดับต่อไปล่าสุด (เมื่อตรวจสอบ) กับโหนดของเราหรือโหนดจากannounceอาเรย์มีสองเหตุการณ์ที่เป็นไปได้: 1. โหนดลำดับที่ประสบความสำเร็จ 2. บางกระทู้อื่น ๆ pre-empted เธรดนี้

ขั้นตอนต่อไปคือการตรวจสอบว่าโหนดที่สร้างขึ้นสำหรับการร้องขอนี้ สิ่งนี้อาจเกิดขึ้นได้เนื่องจากเธรดนี้ประสบความสำเร็จในการจัดลำดับเธรดหรือบางเธรดอื่นหยิบมันขึ้นมาจากannounceอาร์เรย์และจัดลำดับให้เรา หากยังไม่ได้รับการจัดลำดับกระบวนการจะทำซ้ำ มิฉะนั้นการโทรจะเสร็จสิ้นโดยการล้างอาร์เรย์ประกาศสำหรับที่ดัชนีของเธรดนี้และส่งคืนค่าผลลัพธ์ของการเรียกใช้ อาร์เรย์การประกาศจะถูกเคลียร์เพื่อรับประกันว่าไม่มีการอ้างอิงถึงโหนดที่เหลืออยู่รอบ ๆ ซึ่งจะป้องกันไม่ให้โหนดถูกรวบรวมขยะและดังนั้นจึงเก็บโหนดทั้งหมดในรายการที่ลิงก์จากจุดนั้นบนชีวิตบนฮีป

ประเมินวิธีการ

ในตอนนี้โหนดของการเรียกใช้ได้รับการจัดลำดับเรียบร้อยแล้วจำเป็นต้องประเมินการเรียกใช้ ในการทำเช่นนั้นขั้นตอนแรกคือการตรวจสอบให้แน่ใจว่ามีการประเมินการเรียกใช้ก่อนหน้านี้ หากพวกเขาไม่มีเธรดนี้จะไม่รอ แต่จะทำงานทันที

วิธีการ VerifyPrior

ensurePrior()วิธีการทำงานนี้โดยการตรวจสอบโหนดก่อนหน้านี้ในรายการที่เชื่อมโยง หากไม่ได้ตั้งค่าสถานะจะมีการประเมินโหนดก่อนหน้า โหนดที่เป็นแบบเรียกซ้ำ หากโหนดก่อนหน้านี้โหนดก่อนหน้านี้ไม่ได้รับการประเมินก็จะเรียกการประเมินผลสำหรับโหนดนั้นและอื่น ๆ

ขณะนี้ทราบว่าโหนดก่อนหน้านี้มีสถานะเราสามารถประเมินโหนดนี้ได้ โหนดสุดท้ายถูกดึงและกำหนดให้กับตัวแปรโลคัล หากการอ้างอิงนี้เป็นโมฆะก็หมายความว่าเธรดอื่น ๆ ได้ทำการจองข้อมูลนี้ล่วงหน้าแล้วและประเมินโหนดนี้แล้ว การตั้งค่าเป็นรัฐ มิฉะนั้นสถานะของโหนดก่อนหน้านี้จะถูกส่งผ่านไปยังSequentialวิธีการใช้ของวัตถุพร้อมกับการร้องขอของโหนดนี้ สถานะที่ส่งคืนถูกตั้งค่าไว้บนโหนดและtruncate()วิธีการนั้นเรียกว่าเป็นการล้างลิงก์ย้อนกลับจากโหนดเนื่องจากไม่ต้องการอีกต่อไป

วิธีการ MoveForward

วิธีการย้ายไปข้างหน้าจะพยายามย้ายการอ้างอิงส่วนหัวทั้งหมดไปยังโหนดนี้หากพวกเขาไม่ได้ชี้ไปที่บางสิ่งเพิ่มเติมตาม นี่คือเพื่อให้แน่ใจว่าหากเธรดหยุดการโทรส่วนหัวของมันจะไม่เก็บการอ้างอิงไปยังโหนดที่ไม่ต้องการอีกต่อไป compareAndSet()วิธีการที่จะทำให้แน่ใจว่าเราจะอัปเดตโหนดถ้าบางหัวข้ออื่น ๆ ยังไม่ได้เปลี่ยนมันตั้งแต่มันถูกดึง

ประกาศอาร์เรย์และช่วยเหลือ

กุญแจสำคัญในการทำให้วิธีการนี้ปราศจากการรอซึ่งตรงข้ามกับการล็อคฟรีก็คือเราไม่สามารถสันนิษฐานได้ว่าตัวจัดกำหนดการเธรดจะให้ความสำคัญกับเธรดแต่ละรายการเมื่อจำเป็น หากแต่ละเธรดพยายามเรียงลำดับโหนดของตนเองเป็นไปได้ที่เธรดอาจถูกจองไว้ล่วงหน้าอย่างต่อเนื่องภายใต้โหลด ในการพิจารณาถึงความเป็นไปได้นี้แต่ละเธรดจะพยายาม 'ช่วย' เธรดอื่น ๆ ที่อาจไม่สามารถเรียงลำดับได้

แนวคิดพื้นฐานคือเมื่อแต่ละเธรดสร้างโหนดได้สำเร็จลำดับที่ได้รับจะเพิ่มขึ้นอย่างน่าเบื่อ หากเธรดหรือเธรดถูกจองล่วงหน้าเธรดอื่นอย่างต่อเนื่องดัชนีการใช้เพื่อค้นหาโหนดที่ตามมาในannounceอาร์เรย์จะเลื่อนไปข้างหน้า แม้ว่าเธรดทุกอันที่พยายามเรียงลำดับโหนดที่ให้นั้นจะถูกจองไว้ล่วงหน้าอย่างต่อเนื่องโดยเธรดอื่นในที่สุดเธรดทั้งหมดจะพยายามเรียงลำดับโหนดนั้น เพื่อแสดงให้เห็นว่าเราจะสร้างตัวอย่างที่มีสามกระทู้

ที่จุดเริ่มต้นองค์ประกอบทั้งสามของเธรดและประกาศจะถูกชี้ไปที่tailโหนด lastSequenceสำหรับแต่ละหัวข้อคือ 0

ณ จุดนี้เธรด 1ถูกเรียกใช้งานด้วยการเรียกใช้ มันตรวจสอบอาร์เรย์ประกาศว่ามันเป็นลำดับสุดท้าย (ศูนย์) ซึ่งเป็นโหนดที่มีการจัดตารางเวลาให้ดัชนี มันเรียงลำดับโหนดและมันlastSequenceถูกตั้งค่าเป็น 1

ตอนนี้เธรด 2จะถูกดำเนินการด้วยการเรียกใช้มันจะตรวจสอบอาร์เรย์การประกาศที่ลำดับสุดท้าย (ศูนย์) และเห็นว่าไม่ต้องการความช่วยเหลือและพยายามเรียงลำดับการเรียกใช้ มันประสบความสำเร็จและตอนนี้มันlastSequenceถูกตั้งค่าเป็น 2

ตอนนี้เธรด 3ถูกดำเนินการแล้วและยังเห็นว่าโหนดที่announce[0]ได้รับการจัดลำดับแล้วและมีการจัดลำดับเป็นของตนเอง มันlastSequenceเป็นชุดนี้ถึง 3

ตอนนี้เธรด 1ถูกเรียกอีกครั้ง มันตรวจสอบอาร์เรย์ประกาศที่ดัชนี 1 และพบว่ามันถูกจัดลำดับแล้ว ในขณะเดียวกันเธรด 2จะถูกเรียกใช้ มันตรวจสอบประกาศอาร์เรย์ที่ดัชนี 2 และพบว่ามันมีการเรียงลำดับแล้ว ตอนนี้ทั้งเธรด 1และเธรด 2พยายามเรียงลำดับโหนดของตนเอง หัวข้อที่ 2ชนะและมันเรียงตามลำดับการเรียก มันlastSequenceถูกตั้งค่าเป็น 4 ในขณะเดียวกันเธรดที่สามถูกเรียกใช้ มันตรวจสอบดัชนีมันlastSequence(mod 3) และพบว่าโหนดที่announce[0]ไม่ได้ถูกจัดลำดับ เธรด 2จะถูกเรียกใช้อีกครั้งในเวลาเดียวกันกับที่เธรด 1กำลังดำเนินอยู่เป็นครั้งที่สอง ด้าย 1พบว่าการภาวนา unsequenced ที่announce[1]ซึ่งเป็นโหนดเพิ่งสร้างขึ้นโดยกระทู้ 2 พยายามเรียงลำดับการเรียกใช้เธรด 2และทำสำเร็จ เธรด 2พบว่าเป็นโหนดของตนเองannounce[1]และได้รับการจัดลำดับแล้ว มันตั้งค่าเป็นlastSequence5 เธรด 3จะถูกเรียกใช้และพบว่าโหนดที่เธรด 1 ที่วางที่announce[0]ยังไม่ได้ถูกจัดลำดับและพยายามทำเช่นนั้น ในขณะเดียวกันก็มีการเรียกใช้เธรด 2และทำให้เธรด 3 ว่างไว้ล่วงหน้าซึ่งจะเรียงลำดับโหนดและตั้งค่าเป็นlastSequence6

ด้ายไม่ดี1 . แม้ว่าเธรด 3พยายามเรียงลำดับเธรดทั้งสองจะถูกขัดขวางอย่างต่อเนื่องโดยตัวกำหนดตารางเวลา แต่ ณ จุดนี้ ตอนนี้เธรด 2ยังชี้ไปที่announce[0](6 mod 3) ทั้งสามกระทู้ถูกตั้งค่าให้พยายามเรียงลำดับการเรียกใช้เดียวกัน เรื่องที่ประสบความสำเร็จด้ายโหนดถัดไปที่จะติดใจจะภาวนารอกระทู้ 1announce[0]คือโหนดอ้างอิงโดย

นี่เป็นสิ่งที่หลีกเลี่ยงไม่ได้ เพื่อให้เธรดถูกจองล่วงหน้าเธรดอื่นจะต้องเรียงลำดับโหนดและเมื่อทำเช่นนั้นเธรดเหล่านั้นจะเลื่อนlastSequenceไปข้างหน้าอย่างต่อเนื่อง หากโหนดของเธรดที่ระบุไม่ได้ถูกจัดลำดับอย่างต่อเนื่องในที่สุดเธรดทั้งหมดจะชี้ไปที่ดัชนีของมันในอาร์เรย์ประกาศ เธรดจะไม่ทำสิ่งใดจนกว่าโหนดที่พยายามจะช่วยได้รับการจัดลำดับสถานการณ์กรณีที่เลวร้ายที่สุดคือเธรดทั้งหมดจะชี้ไปยังโหนดที่ตามมาแบบเดียวกัน ดังนั้นเวลาที่ต้องใช้ในการจัดลำดับการเรียกใช้ใด ๆ เป็นฟังก์ชันของจำนวนเธรดไม่ใช่ขนาดของอินพุต


คุณจะช่วยวางรหัสที่ตัดตอนมาบางส่วนบน Pastebin หรือไม่? หลายสิ่งหลายอย่าง (เช่นรายการที่เชื่อมโยง lockfree) สามารถระบุได้ง่ายๆเช่นนี้? เป็นการยากที่จะเข้าใจคำตอบของคุณโดยรวมเมื่อมีรายละเอียดมากมาย ไม่ว่าในกรณีใดมันก็ดูมีแนวโน้มฉันก็อยากจะเจาะลึกลงไปในสิ่งที่มันรับประกัน
VF1

ดูเหมือนว่านี่จะเป็นการใช้งานแบบไม่มีล็อคที่ถูกต้อง แต่มันหายไปจากปัญหาพื้นฐานที่ฉันกังวล ข้อกำหนดของ linearizability จำเป็นต้องมี "ประวัติที่ถูกต้อง" ที่จะนำเสนอซึ่งในกรณีของการใช้งานลิสต์ลิสต์จะต้องมีแอพpreviousและพอยน์เตอร์nextที่ถูกต้อง การบำรุงรักษาและสร้างประวัติที่ถูกต้องในลักษณะที่ปราศจากการรอดูเหมือนว่ายาก
VF1

@ VF1 ฉันไม่แน่ใจว่าปัญหาไม่ได้รับการแก้ไข ทุกสิ่งที่คุณพูดถึงในความคิดเห็นที่เหลือนั้นจะกล่าวถึงในตัวอย่างที่ฉันให้จากสิ่งที่ฉันสามารถบอกได้
JimmyJames

คุณสละคุณสมบัติที่ปราศจากการรอคอย
VF1

@ VF1 คุณคิดยังไง?
JimmyJames

0

คำตอบก่อนหน้าของฉันไม่ตอบคำถามได้อย่างถูกต้อง แต่เมื่อ OP เห็นว่ามีประโยชน์ฉันจะปล่อยให้มันเป็นอย่างนั้น ตามรหัสในลิงก์ของคำถามนี่คือความพยายามของฉัน ฉันได้ทำการทดสอบพื้นฐานจริง ๆ เท่านั้น แต่ดูเหมือนว่าจะคำนวณค่าเฉลี่ยอย่างเหมาะสม คำติชมยินดีว่าเป็นสิ่งที่ปราศจากการรอคอยอย่างถูกต้องหรือไม่

หมายเหตุ : ฉันลบ Universal Interface และทำให้เป็นคลาส การมี Universal นั้นประกอบไปด้วย Sequentials และอย่างใดอย่างหนึ่งดูเหมือนเป็นเรื่องยุ่งยากที่ไม่จำเป็น แต่ฉันอาจจะพลาดอะไรบางอย่างไป volatileในระดับเฉลี่ยที่ฉันได้ทำเครื่องหมายตัวแปรรัฐจะเป็น ไม่จำเป็นต้องทำให้รหัสใช้งานได้ เป็นคนหัวอนุรักษ์ (เป็นความคิดที่ดีกับเธรด) และป้องกันไม่ให้แต่ละเธรดทำการคำนวณทั้งหมด (หนึ่งครั้ง)

ลำดับและโรงงาน

public interface Sequential<E, S, R>
{ 
  R apply(S priorState);

  S state();

  default boolean isApplied()
  {
    return state() != null;
  }
}

public interface Factory<E, S, R>
{
   S initial();

   Sequential<E, S, R> generate(E input);
}

สากล

import java.util.concurrent.ConcurrentLinkedQueue;

public class Universal<I, S, R> 
{
  private final Factory<I, S, R> generator;
  private final ConcurrentLinkedQueue<Sequential<I, S, R>> wfq = new ConcurrentLinkedQueue<>();
  private final ThreadLocal<Sequential<I, S, R>> last = new ThreadLocal<>();

  public Universal(Factory<I, S, R> g)
  { 
    generator = g;
  }

  public R apply(I invocation)
  {
    Sequential<I, S, R> newSequential = generator.generate(invocation);
    wfq.add(newSequential);

    Sequential<I, S, R> last = null;
    S prior = generator.initial(); 

    for (Sequential<I, S, R> i : wfq) {
      if (!i.isApplied() || newSequential == i) {
        R r = i.apply(prior);

        if (i == newSequential) {
          wfq.remove(last.get());
          last.set(newSequential);

          return r;
        }
      }

      prior = i.state();
    }

    throw new IllegalStateException("Houston, we have a problem");
  }
}

เฉลี่ย

public class Average implements Sequential<Integer, Average.State, Double>
{
  private final Integer invocation;
  private volatile State state;

  private Average(Integer invocation)
  {
    this.invocation = invocation;
  }

  @Override
  public Double apply(State prior)
  {
    System.out.println(Thread.currentThread() + " " + invocation + " prior " + prior);

    state = prior.add(invocation);

    return ((double) state.sum)/ state.count;
  }

  @Override
  public State state()
  {
    return state;
  }

  public static class AverageFactory implements Factory<Integer, State, Double> 
  {
    @Override
    public State initial()
    {
      return new State(0, 0);
    }

    @Override
    public Average generate(Integer i)
    {
      return new Average(i);
    }
  }

  public static class State
  {
    private final int sum;
    private final int count;

    private State(int sum, int count)
    {
      this.sum = sum;
      this.count = count;
    }

    State add(int value)
    {
      return new State(sum + value, count + 1);
    }

    @Override
    public String toString()
    {
      return sum + " / " + count;
    }
  }
}

รหัสตัวอย่าง

private static final int THREADS = 10;
private static final int SIZE = 50;

public static void main(String... args)
{
  Average.AverageFactory factory = new Average.AverageFactory();

  Universal<Integer, Average.State, Double> universal = new Universal<>(factory);

  for (int i = 0; i < THREADS; i++)
  {
    new Thread(new Test(i * SIZE, universal)).start();
  }
}

static class Test implements Runnable
{
  final int start;
  final Universal<Integer, Average.State, Double> universal;

  Test(int start, Universal<Integer, Average.State, Double> universal)
  {
    this.start = start;
    this.universal = universal;
  }

  @Override
  public void run()
  {
    for (int i = start; i < start + SIZE; i++)
    {
      System.out.println(Thread.currentThread() + " " + i);

      System.out.println(System.nanoTime() + " " + Thread.currentThread() + " " + i + " result " + universal.apply(i));
    }
  }
}

ฉันทำการแก้ไขโค้ดขณะที่โพสต์ไว้ที่นี่ มันควรจะโอเค แต่แจ้งให้เราทราบหากคุณมีปัญหา


คุณไม่ต้องตอบคำถามอื่น ๆ ของฉัน (ฉันได้อัปเดตคำถามของฉันก่อนหน้านี้เพื่อให้ได้ข้อสรุปที่เกี่ยวข้องที่จะดึงออกมาจากมัน) น่าเสียดายที่คำตอบนี้ไม่ได้ตอบคำถามอย่างใดอย่างหนึ่งเนื่องจากมันไม่ได้เพิ่มหน่วยความจำใด ๆ ในwfqจริงดังนั้นคุณยังต้องสำรวจผ่านประวัติศาสตร์ทั้งหมด - รันไทม์ไม่ได้รับการปรับปรุงยกเว้นปัจจัยคงที่
VF1

@ Vf1 เวลาที่ใช้ในการสำรวจรายการทั้งหมดเพื่อตรวจสอบว่ามีการคำนวณหรือไม่นั้นจะเป็น miniscule เปรียบเทียบกับการคำนวณแต่ละครั้ง เนื่องจากไม่จำเป็นต้องใช้สถานะก่อนหน้าจึงควรจะสามารถลบสถานะเริ่มต้นได้ การทดสอบเป็นเรื่องยากและอาจต้องใช้คอลเลกชันที่กำหนดเอง แต่ฉันได้เพิ่มการเปลี่ยนแปลงเล็กน้อย
JimmyJames

@ VF1 อัปเดตไปยังการใช้งานที่ดูเหมือนว่าจะทำงานกับการทดสอบคร่าวๆขั้นพื้นฐาน ฉันไม่แน่ใจว่าปลอดภัย แต่อยู่ด้านบนของหัวของฉันหากสากลทราบถึงเธรดที่ใช้งานได้มันสามารถติดตามแต่ละเธรดและลบองค์ประกอบออกเมื่อเธรดทั้งหมดผ่านพวกเขาอย่างปลอดภัย
JimmyJames

@ VF1 ดูรหัสสำหรับ ConcurrentLinkedQueue วิธีการเสนอมีลูปคล้ายกับที่คุณอ้างว่าได้ทำคำตอบอื่น ๆ โดยไม่ต้องรอ ค้นหาความคิดเห็น "Lost CAS race ไปยังเธรดอื่นอ่านอีกครั้ง"
JimmyJames

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