ความไม่สามารถเปลี่ยนรูปแบบได้ทั้งหมดไม่จำเป็นต้องล็อคในการเขียนโปรแกรมแบบหลายโปรเซสเซอร์หรือไม่?


39

ส่วนที่ 1

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

ฉันรู้ว่าโปรแกรมเมอร์และนักคณิตศาสตร์ที่ใช้งานได้จำนวนมากชอบพูดถึง "ไม่มีผลข้างเคียง" แต่ใน "โลกแห่งความจริง" ทุกอย่างมีผลข้างเคียงแม้ว่าจะถึงเวลาที่ต้องใช้คำสั่งเครื่อง ฉันสนใจทั้งคำตอบเชิงทฤษฎี / วิชาการและคำตอบเชิงปฏิบัติ / โลกแห่งความจริง

หากความไม่สามารถเปลี่ยนแปลงได้ปลอดภัยตามขอบเขตหรือสมมติฐานบางอย่างฉันต้องการทราบว่าขอบเขตของ "เขตปลอดภัย" คืออะไรกันแน่ ตัวอย่างของขอบเขตที่เป็นไปได้:

  • I / O
  • ยกเว้น / ข้อผิดพลาด
  • การโต้ตอบกับโปรแกรมที่เขียนเป็นภาษาอื่น
  • การโต้ตอบกับเครื่องอื่น ๆ (ทางกายภาพเสมือนหรือตามทฤษฎี)

ขอขอบคุณเป็นพิเศษสำหรับ @JimmaHoffa สำหรับความคิดเห็นของเขาซึ่งเริ่มต้นคำถามนี้!

ส่วนที่ 2

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

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

สรุป

ฉันกำลังรวมคำถามสองข้อนี้ไว้ในคำถามเดียวเพื่อพยายามไปให้ถึงจุดที่ขอบเขตสำหรับการไม่สามารถแก้ไขปัญหาได้


21
but everything has a side effect- เอ่อไม่มันไม่ได้ ฟังก์ชั่นที่ยอมรับค่าบางอย่างและส่งคืนค่าอื่นและไม่รบกวนสิ่งใดนอกฟังก์ชั่นไม่มีผลข้างเคียงดังนั้นจึงปลอดภัยต่อเธรด ไม่สำคัญว่าคอมพิวเตอร์จะใช้ไฟฟ้า เราสามารถพูดคุยเกี่ยวกับรังสีคอสมิกที่กระทบเซลล์หน่วยความจำได้ด้วยหากคุณต้องการ แต่ขอโต้แย้งกัน หากคุณต้องการพิจารณาสิ่งต่าง ๆ เช่นวิธีการใช้งานฟังก์ชั่นที่มีผลต่อการใช้พลังงานนั่นเป็นปัญหาที่แตกต่างจากการเขียนโปรแกรม threadsafe
Robert Harvey

5
@RobertHarvey - บางทีฉันแค่ใช้คำจำกัดความที่แตกต่างของผลข้างเคียงและฉันควรจะพูดว่า "ผลข้างเคียงที่เกิดขึ้นจริง" แทน ใช่นักคณิตศาสตร์มีฟังก์ชั่นที่ไม่มีผลข้างเคียง รหัสที่ประมวลผลบนเครื่องในโลกแห่งความเป็นจริงนั้นใช้ทรัพยากรของเครื่องในการดำเนินการไม่ว่าจะเป็นการแปลงข้อมูลหรือไม่ ฟังก์ชั่นในตัวอย่างของคุณวางค่าส่งคืนในสแต็กในสถาปัตยกรรมเครื่องส่วนใหญ่
GlenPeterson

1
ถ้าคุณจริงจะได้รับผ่านมันผมคิดว่าคำถามของคุณไปที่หัวใจของการวิจัยนี้น่าอับอายresearch.microsoft.com/en-us/um/people/simonpj/papers/...
จิมมี่ฮอฟฟา

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

1
@RobertHarvey spoonerism; Monads heh และคุณสามารถรวบรวมได้จากหน้าคู่แรก ฉันพูดถึงมันเพราะในช่วงเวลาทั้งหมดรายละเอียดเทคนิคในการจัดการกับผลข้างเคียงอย่างบริสุทธิ์ฉันค่อนข้างแน่ใจว่าจะตอบคำถามของ Glen ดังนั้นจึงโพสต์ไว้เป็นบันทึกย่อที่ดีสำหรับทุกคนที่พบคำถามนี้ใน อนาคตสำหรับการอ่านเพิ่มเติม
จิมมี่ฮอฟฟา

คำตอบ:


35

นี่เป็นคำถามที่มีประโยคแปลก ๆ ที่กว้างจริงๆถ้าตอบอย่างเต็มที่ ฉันจะมุ่งเน้นไปที่การล้างข้อมูลจำเพาะบางอย่างที่คุณถาม

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

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

ปรากฎว่ามีหลายสิ่งที่อ่านวัตถุ IO ทำ แต่ IO เองมีแนวโน้มที่จะไม่ใช้งานพร้อมกันได้ดี การประมวลผลเกือบทั้งหมดทำได้ แต่วัตถุอื่นอาจไม่แน่นอนหรือการประมวลผลเองอาจใช้สถานะที่ไม่เป็นมิตรกับการเกิดพร้อมกัน การคัดลอกวัตถุเป็นปัญหาใหญ่ที่ซ่อนอยู่ในบางภาษาเนื่องจากการทำสำเนาแบบเต็ม (เกือบ) ไม่เคยมีการใช้งานแบบอะตอมมิก นี่คือสิ่งที่วัตถุที่เปลี่ยนไม่ได้ช่วยคุณ

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

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

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


+1 สิ่งนี้สมเหตุสมผล แต่คุณสามารถยกตัวอย่างของภาษาที่ไม่เปลี่ยนรูปลึกที่คุณยังต้องกังวลเกี่ยวกับการจัดการภาวะพร้อมกันอย่างเหมาะสมหรือไม่? คุณระบุว่าคุณทำ แต่สถานการณ์ดังกล่าวไม่ชัดเจนสำหรับฉัน
Jimmy Hoffa

@JimmyHoffa ในภาษาที่ไม่เปลี่ยนรูปคุณยังต้องมีวิธีในการอัพเดตสถานะระหว่างเธรด สองภาษาที่ไม่เปลี่ยนรูปแบบที่สุดที่ฉันรู้ (Clojure และ Haskell) ให้ประเภทการอ้างอิง (อะตอมและ Mvars) ที่ให้วิธีการจัดส่งรัฐแก้ไขระหว่างหัวข้อ ความหมายของประเภทอ้างอิงของพวกเขาป้องกันข้อผิดพลาดบางประเภทที่เกิดขึ้นพร้อมกัน แต่คนอื่น ๆ ยังคงเป็นไปได้
หินใน

@stonemetal น่าสนใจใน 4 เดือนของฉันกับ Haskell ฉันยังไม่เคยได้ยิน Mvars เลยฉันเคยได้ยินว่าใช้ STM สำหรับการสื่อสารสถานะพร้อมกันซึ่งทำหน้าที่เหมือนข้อความของ Erlang ที่ฉันคิด แม้ว่าตัวอย่างที่สมบูรณ์แบบของการไม่เปลี่ยนรูปแบบที่ไม่สามารถแก้ไขปัญหาที่เกิดขึ้นพร้อมกันที่ฉันคิดคือการอัปเดต UI หากคุณมี 2 เธรดที่พยายามอัปเดต UI ด้วยข้อมูลเวอร์ชันที่ต่างกันหนึ่งอาจเป็นรุ่นใหม่กว่า สภาพการแข่งขันที่คุณต้องรับประกันการเรียงลำดับอย่างใด .. ความคิดที่น่าสนใจ .. ขอบคุณสำหรับรายละเอียด
Jimmy Hoffa

1
@jimmyhoffa - ตัวอย่างที่พบบ่อยที่สุดคือ IO แม้ว่าภาษานั้นจะไม่เปลี่ยนรูป แต่ฐานข้อมูล / เว็บไซต์ / ไฟล์ของคุณก็ไม่ได้เป็นเช่นนั้น อีกอย่างคือแผนที่ปกติ / การลดลงของคุณ การเปลี่ยนไม่ได้หมายความว่าการรวมแผนที่เป็นสิ่งที่เข้าใจผิดได้มากกว่า แต่คุณยังต้องจัดการกับ 'เมื่อแผนที่ทั้งหมดทำเสร็จพร้อมกันลดการประสานงาน'
Telastyn

1
@JimmyHoffa: MVars เป็นระดับพื้นฐานที่เกิดขึ้นพร้อมกันในระดับต่ำที่ไม่แน่นอนที่เกิดขึ้นพร้อมกัน (ในทางเทคนิคการอ้างอิงที่ไม่เปลี่ยนรูปไปยังที่เก็บข้อมูลที่ไม่แน่นอน) ไม่แตกต่างจากสิ่งที่คุณเห็นในภาษาอื่น ๆ การหยุดชะงักและสภาพการแข่งขันเป็นไปได้มาก STM เป็นสิ่งที่เกิดขึ้นพร้อมกันในระดับสูงสำหรับหน่วยความจำที่แบ่งใช้ที่ไม่สามารถล็อคได้ซึ่งไม่มีการล็อค (แตกต่างอย่างมากจากการส่งข้อความ) ที่อนุญาตให้ทำธุรกรรมแบบผสมได้โดยไม่มีความเป็นไปได้ที่จะเกิด deadlocks หรือสภาวะการแข่งขัน ข้อมูลที่ไม่เปลี่ยนรูปนั้นเป็นเพียงหัวข้อที่ปลอดภัยไม่มีอะไรจะพูดเกี่ยวกับมัน
CA McCann

13

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

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

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

สมมติว่าฉันต้องการฟังก์ชั่นง่าย ๆ ที่ยอมรับการรวบรวมสตริงและส่งคืนสตริงที่เป็นการรวมกันของสตริงทั้งหมดในการรวบรวมที่คั่นด้วยเครื่องหมายจุลภาค การติดตั้งที่ง่ายและไร้เดียงสาใน C # อาจมีลักษณะเช่นนี้:

public string ConcatenateWithCommas(ImmutableList<string> list)
{
    string result = string.Empty;
    bool isFirst = false;

    foreach (string s in list)
    {
        if (isFirst)
            result += s;
        else
            result += ", " + s;
    }
    return result;
} 

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

ตอนนี้สมมติว่าฉันทำสิ่งนี้:

public string ConcatenateWithCommas(ImmutableList<string> list)
{
    var result = new StringBuilder();
    bool isFirst = false;

    foreach (string s in list)
    {
        if (isFirst)
            result.Append(s);
        else
            result.Append(", " + s);
    }
    return result.ToString();
} 

แจ้งให้ทราบว่าได้ถูกแทนที่ด้วยวัตถุไม่แน่นอนstring result StringBuilderนี้เป็นมากเร็วกว่าตัวอย่างแรกเพราะสตริงใหม่จะไม่ได้สร้างในแต่ละครั้งที่ผ่านห่วง แต่วัตถุ StringBuilder จะเพิ่มอักขระจากแต่ละสตริงลงในชุดของอักขระเท่านั้นและแสดงผลลัพธ์ทั้งหมดในตอนท้าย

ฟังก์ชั่นนี้ไม่เปลี่ยนรูปแม้ว่า StringBuilder จะไม่แน่นอน

ใช่แล้ว. ทำไม? เนื่องจากแต่ละครั้งที่เรียกฟังก์ชันนี้จะมีการสร้าง StringBuilder ใหม่สำหรับการโทรนั้นเท่านั้น ดังนั้นตอนนี้เรามีฟังก์ชั่นแท้ที่ปลอดภัยต่อเธรด แต่มีส่วนประกอบที่ไม่แน่นอน

แต่ถ้าฉันทำสิ่งนี้

public class Concatenate
{
    private StringBuilder result = new StringBuilder();
    bool isFirst = false;

    public string ConcatenateWithCommas(ImmutableList<string> list)
    {
        foreach (string s in list)
        {
            if (isFirst)
                result.Append(s);
            else
                result.Append(", " + s);
        }
        return result.ToString();
    } 
}

วิธีนี้ปลอดภัยไหม? ไม่มันไม่ใช่ ทำไม? เพราะชั้นเรียนตอนนี้ถือสถานะซึ่งวิธีการของฉันขึ้นอยู่กับ สภาพการแย่งชิงมีอยู่ในวิธีการ: เธรดหนึ่งอาจแก้ไขIsFirstแต่เธรดอื่นอาจดำเนินการแรกAppend()ซึ่งในกรณีนี้ฉันมีเครื่องหมายจุลภาคที่จุดเริ่มต้นของสตริงซึ่งไม่ควรจะมี

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

อย่างไรก็ตามเพื่อแก้ไขฉันใส่lockคำสั่งรอบอวัยวะภายในของวิธีการ

public class Concatenate
{
    private StringBuilder result = new StringBuilder();
    bool isFirst = false;
    private static object locker = new object();

    public string AppendWithCommas(ImmutableList<string> list)
    {
        lock (locker)
        {
            foreach (string s in list)
            {
                if (isFirst)
                    result.Append(s);
                else
                    result.Append(", " + s);
            }
            return result.ToString();
        }
    } 
}

ตอนนี้มันปลอดภัยสำหรับเธรดอีกครั้ง

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

สำหรับตัวอย่างของวิธีการดำเนินงานที่อาจจะรั่วไหลออกมาในสถานการณ์ที่เห็นพ้องให้ดูที่นี่


2
ยกเว้นว่าฉันเข้าใจผิดเพราะ a Listไม่แน่นอนในฟังก์ชันแรกที่คุณอ้างว่า 'บริสุทธิ์' เธรดอื่นสามารถลบองค์ประกอบทั้งหมดออกจากรายการหรือเพิ่มจำนวนมากขึ้นในขณะที่อยู่ในลูป foreach ไม่แน่ใจว่ามันจะเล่นอย่างไรกับการIEnumeratorถูกwhile(iter.MoveNext())เอ็ด แต่ถ้าหากIEnumeratorมันไม่เปลี่ยนแปลง
Jimmy Hoffa

จริงคุณต้องสมมติว่าไม่มีการเขียนคอลเลกชันในขณะที่เธรดกำลังอ่านจากมัน นั่นจะเป็นสมมติฐานที่ถูกต้องหากแต่ละเธรดที่เรียกเมธอดสร้างรายการของตัวเอง
Robert Harvey

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

1
@ จิมมี่ฮอฟฟา: บ้าคุณช่วยให้ฉันหมกมุ่นกับปัญหาไก่และไข่! หากคุณเห็นวิธีแก้ปัญหาที่ใดก็ได้โปรดแจ้งให้เราทราบ
Robert Harvey

1
เพิ่งเจอคำตอบนี้และมันเป็นหนึ่งในคำอธิบายที่ดีที่สุดในหัวข้อที่ฉันเจอตัวอย่างที่กระชับและทำให้ง่ายต่อการอ่าน ขอบคุณ!
Stephen Byrne

4

ฉันไม่แน่ใจถ้าฉันเข้าใจคำถามของคุณ

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

คำตอบของส่วนที่ 2 - ล็อคควรช้ากว่าล็อคไม่ได้


3
ส่วนที่สองถามว่า "การแลกเปลี่ยนประสิทธิภาพระหว่างการล็อกและโครงสร้างที่ไม่เปลี่ยนรูปแบบคืออะไร" มันอาจสมควรได้รับคำถามของตัวเองถ้ามันตอบได้
Robert Harvey

4

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

do
{
   oldState = someObject.State;
   newState = oldState.WithSomeChanges();
} while (Interlocked.CompareExchange(ref someObject.State, newState, oldState) != oldState;

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

รูปแบบนี้มีข้อได้เปรียบที่เธรดซึ่งได้รับ waylaid ไม่สามารถบล็อกความคืบหน้าของเธรดอื่น ๆ มันมีข้อได้เปรียบเพิ่มเติมที่แม้ว่าจะมีการโต้แย้งกันอย่างหนัก แต่ก็มีบางหัวข้อที่จะดำเนินการต่อไป แม้ว่าจะมีข้อเสีย แต่ในการที่มีการโต้เถียงกันหลายคนอาจใช้เวลามากในการทำงานซึ่งพวกเขาจะจบลงด้วยการทิ้ง ตัวอย่างเช่นหาก 30 เธรดในซีพียูที่แยกต่างหากทั้งหมดพยายามเปลี่ยนวัตถุพร้อมกันหนึ่งจะประสบความสำเร็จในความพยายามครั้งแรกหนึ่งในสองของมันหนึ่งในสามของมัน ฯลฯ เพื่อให้แต่ละกระทู้จบลงโดยเฉลี่ยทำให้ประมาณ 15 ครั้ง เพื่ออัปเดตข้อมูล การใช้การล็อก "คำแนะนำ" สามารถปรับปรุงสิ่งต่าง ๆ ได้อย่างมีนัยสำคัญ: ก่อนที่เธรดจะพยายามอัปเดตควรตรวจสอบว่ามีการตั้งค่าตัวบ่งชี้ "contention" หรือไม่ ถ้าเป็นเช่นนั้น มันควรจะได้รับการล็อคก่อนทำการอัพเดท หากเธรดพยายามไม่สำเร็จสองสามครั้งที่การอัพเดตควรตั้งค่าสถานะ contention หากเธรดที่พยายามรับการล็อกพบว่าไม่มีใครรออยู่ก็ควรล้างการตั้งค่าสถานะ contention โปรดทราบว่าการล็อคที่นี่ไม่จำเป็นสำหรับ "ความถูกต้อง"; รหัสจะทำงานอย่างถูกต้องแม้จะไม่มีมัน วัตถุประสงค์ของการล็อคคือการลดจำนวนรหัสเวลาที่ใช้ไปกับการทำงานที่ไม่น่าจะสำเร็จ


4

คุณเริ่มต้นด้วย

ชัดเจน Immutability ลดความต้องการล็อคในการเขียนโปรแกรมแบบมัลติโปรเซสเซอร์

ไม่ถูกต้อง. คุณต้องอ่านเอกสารประกอบอย่างละเอียดสำหรับทุกชั้นเรียนที่คุณใช้ ตัวอย่างเช่น const std :: string ใน C ++ ไม่ปลอดภัยสำหรับเธรด วัตถุที่ไม่เปลี่ยนรูปสามารถมีสถานะภายในที่เปลี่ยนแปลงเมื่อเข้าถึงวัตถุเหล่านั้น

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

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


2
const std::stringเป็นตัวอย่างที่ไม่ดีและปลาเฮอริ่งแดงนิดหน่อย สตริง C ++ นั้นผันแปรได้และconstไม่สามารถรับรองว่าไม่สามารถเปลี่ยนแปลงได้ สิ่งที่มันทำคือพูดเฉพาะconstฟังก์ชั่นเท่านั้นที่อาจถูกเรียก อย่างไรก็ตามฟังก์ชั่นเหล่านั้นยังคงสามารถเปลี่ยนสถานะภายในและconstสามารถโยนออกไป ในที่สุดก็มีปัญหาเช่นเดียวกับภาษาอื่น ๆ : เพียงเพราะการอ้างอิงของฉันconstไม่ได้หมายความว่าการอ้างอิงของคุณก็เช่นกัน ไม่ควรใช้โครงสร้างข้อมูลที่เปลี่ยนแปลงไม่ได้อย่างแท้จริง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.