ตามที่กล่าวไว้ข้างต้น แต่ละเธรดมีทรัพยากรของตัวเอง แต่พื้นที่โค้ดถูกใช้ร่วมกัน นั่นคือ แต่ละเธรดสามารถดำเนินการฟังก์ชันเดียวกันได้ ปัญหานี้อาจเกิดขึ้นคือหลายเธรดเรียกใช้ฟังก์ชันพร้อมกัน ทำให้เกิดความสับสนของข้อมูลและผลลัพธ์ที่คาดเดาไม่ได้ ดังนั้นเราจึงต้องหลีกเลี่ยงสถานการณ์นี้
C# จัดเตรียมการล็อกคำหลัก ซึ่งสามารถกำหนดส่วนของโค้ดเป็นส่วนที่ไม่เกิดร่วมกัน (ส่วนวิกฤต) ส่วนที่ไม่เกิดร่วมกันจะอนุญาตให้มีเธรดเดียวเท่านั้นที่เข้าสู่การดำเนินการในแต่ละครั้ง ในขณะที่เธรดอื่นๆ ต้องรอ ใน C# การล็อคคีย์เวิร์ดถูกกำหนดไว้ดังนี้:
ล็อค (การแสดงออก) คำสั่ง_block
นิพจน์แสดงถึงวัตถุที่คุณต้องการติดตาม ซึ่งโดยปกติจะเป็นการอ้างอิงวัตถุ
state_block คือโค้ดของส่วนที่ไม่เกิดร่วมกัน รหัสนี้สามารถดำเนินการได้ครั้งละหนึ่งเธรดเท่านั้น
ต่อไปนี้เป็นตัวอย่างทั่วไปของการใช้คีย์เวิร์ด lock มีการอธิบายการใช้งานและวัตถุประสงค์ของคีย์เวิร์ด lock ในความคิดเห็น
ตัวอย่างมีดังนี้:
รหัส
ใช้ระบบ;
โดยใช้ System.Threading;
เนมสเปซ ThreadSimple
-
บัญชีคลาสภายใน
-
สมดุลภายใน;
สุ่ม r = สุ่มใหม่ ();
บัญชีภายใน (เริ่มต้น int)
-
ยอดคงเหลือ = เริ่มต้น;
-
ถอน int ภายใน (จำนวน int)
-
ถ้า (ยอดคงเหลือ < 0)
-
//หากยอดคงเหลือน้อยกว่า 0 ให้ส่งข้อยกเว้น
โยนข้อยกเว้นใหม่ ("ยอดคงเหลือติดลบ");
-
//รับประกันว่าโค้ดต่อไปนี้จะเสร็จสมบูรณ์ก่อนที่เธรดปัจจุบันจะแก้ไขค่าของความสมดุล
// ไม่มีเธรดอื่นใดที่จะเรียกใช้โค้ดนี้เพื่อแก้ไขค่าความสมดุล
//ดังนั้นมูลค่าของยอดคงเหลือต้องไม่ต่ำกว่า 0
ล็อค(นี้)
-
Console.WriteLine("เธรดปัจจุบัน:"+Thread.CurrentThread.Name);
//หากไม่มีการป้องกันคีย์เวิร์ด lock อาจเกิดขึ้นหลังจากดำเนินการตัดสินแบบมีเงื่อนไขแล้ว
//อีกเธรดที่ดำเนินการ balance=balance-amount และแก้ไขค่าของ balance
//การแก้ไขนี้ไม่สามารถมองเห็นได้ในเธรดนี้ ดังนั้นอาจทำให้เงื่อนไข if ไม่คงอยู่อีกต่อไป
//อย่างไรก็ตาม เธรดนี้ยังคงดำเนินการ balance=balance-amount ดังนั้นยอดคงเหลืออาจน้อยกว่า 0
ถ้า (ยอดคงเหลือ >= จำนวน)
-
กระทู้.สลีป(5);
ยอดคงเหลือ = ยอดคงเหลือ - จำนวน;
จำนวนเงินที่ส่งคืน;
-
อื่น
-
ส่งคืน 0; // ธุรกรรมถูกปฏิเสธ
-
-
-
DoTransactions เป็นโมฆะภายใน ()
-
สำหรับ (int i = 0; i < 100; i++)
ถอน(r.ถัดไป(-50, 100));
-
-
การทดสอบชั้นเรียนภายใน
-
เธรดภายในแบบคงที่ [] เธรด = เธรดใหม่ [10];
โมฆะคงที่สาธารณะ Main()
-
บัญชีบัญชี = บัญชีใหม่ (0);
สำหรับ (int i = 0; i < 10; i++)
-
เธรด t = เธรดใหม่ (ThreadStart ใหม่ (acc.DoTransactions));
เธรด[i] = เสื้อ;
-
สำหรับ (int i = 0; i < 10; i++)
เธรด[i].ชื่อ=i.ToString();
สำหรับ (int i = 0; i < 10; i++)
กระทู้[i].เริ่มต้น();
Console.ReadLine();
-
-
} คลาสมอนิเตอร์ล็อคอ็อบเจ็กต์
เมื่อหลายเธรดแชร์อ็อบเจ็กต์ ปัญหาที่คล้ายกับโค้ดทั่วไปจะเกิดขึ้นเช่นกัน สำหรับปัญหาประเภทนี้ ไม่ควรใช้คีย์เวิร์ด lock ใน System.Threading เราเรียกมันว่ามอนิเตอร์ได้ , Monitor มอบโซลูชันสำหรับเธรดเพื่อแชร์ทรัพยากร
คลาส Monitor สามารถล็อควัตถุ และเธรดสามารถดำเนินการบนวัตถุได้ก็ต่อเมื่อได้รับการล็อคเท่านั้น กลไกการล็อกวัตถุช่วยให้แน่ใจว่า เธรดเดียวเท่านั้นที่สามารถเข้าถึงวัตถุนี้ในแต่ละครั้งในสถานการณ์ที่อาจทำให้เกิดความสับสนวุ่นวาย Monitor ต้องเชื่อมโยงกับวัตถุเฉพาะ แต่เนื่องจากเป็นคลาสแบบคงที่ จึงไม่สามารถใช้ในการกำหนดวัตถุได้ และวิธีการทั้งหมดเป็นแบบคงที่และไม่สามารถอ้างอิงโดยใช้วัตถุได้ รหัสต่อไปนี้แสดงให้เห็นถึงการใช้จอภาพเพื่อล็อควัตถุ:
-
คิว oQueue=คิวใหม่();
-
Monitor.Enter(oคิว);
......//ตอนนี้วัตถุ oQueue สามารถจัดการได้โดยเธรดปัจจุบันเท่านั้น
Monitor.Exit(oQueue);//ปลดล็อค
ดังที่แสดงไว้ด้านบน เมื่อเธรดเรียกใช้เมธอด Monitor.Enter() เพื่อล็อคอ็อบเจ็กต์ เธรดนั้นจะเป็นเจ้าของ หากเธรดอื่นต้องการเข้าถึงอ็อบเจ็กต์นี้ พวกเขาสามารถรอให้เธรดนั้นใช้ Monitor.Exit() เท่านั้น วิธีปลดล็อค เพื่อให้แน่ใจว่าเธรดจะปลดล็อคในที่สุด คุณสามารถเขียนเมธอด Monitor.Exit() ในบล็อกโค้ดสุดท้ายในโครงสร้าง try-catch-finally
สำหรับวัตถุใด ๆ ที่ถูกล็อคโดย Monitor ข้อมูลบางอย่างที่เกี่ยวข้องกับวัตถุนั้นจะถูกจัดเก็บไว้ในหน่วยความจำ:
หนึ่งคือการอ้างอิงถึงเธรดที่ล็อคอยู่ในปัจจุบัน
ประการที่สองคือคิวการเตรียมการซึ่งจัดเก็บเธรดที่พร้อมสำหรับการล็อค
ประการที่สามคือคิวที่รอ ซึ่งเก็บการอ้างอิงถึงคิวที่กำลังรอให้สถานะของออบเจ็กต์เปลี่ยนแปลง
เมื่อเธรดที่เป็นเจ้าของการล็อคอ็อบเจ็กต์พร้อมที่จะปลดล็อค จะใช้เมธอด Monitor.Pulse() เพื่อแจ้งเตือนเธรดแรกในคิวที่รอ ดังนั้นเธรดจะถูกถ่ายโอนไปยังคิวการเตรียมการ เมื่อปลดล็อคออบเจ็กต์ ในคิวการเตรียมการ เธรดสามารถรับการล็อคอ็อบเจ็กต์ได้ทันที
ต่อไปนี้เป็นตัวอย่างที่แสดงวิธีใช้คีย์เวิร์ด lock และคลาส Monitor เพื่อใช้การซิงโครไนซ์เธรดและการสื่อสาร นอกจากนี้ยังเป็นปัญหาของผู้ผลิตและผู้บริโภคทั่วไปอีกด้วย
ในรูทีนนี้ เธรดผู้ผลิตและเธรดผู้บริโภคสลับกัน ผู้ผลิตเขียนตัวเลข และผู้บริโภคจะอ่านและแสดงผลทันที (สาระสำคัญของโปรแกรมถูกนำเสนอในความคิดเห็น)
เนมสเปซของระบบที่ใช้มีดังนี้:
ใช้ระบบ;
โดยใช้ System.Threading;
ขั้นแรก ให้กำหนดคลาส Cell ของอ็อบเจ็กต์ที่กำลังดำเนินการอยู่ ในคลาสนี้ มีสองวิธี: ReadFromCell() และ WriteToCell เธรดผู้บริโภคจะเรียก ReadFromCell() เพื่ออ่านเนื้อหาของ cellContents และแสดงเนื้อหานั้น และกระบวนการของผู้ผลิตจะเรียกใช้เมธอด WriteToCell() เพื่อเขียนข้อมูลไปยัง cellContents
ตัวอย่างมีดังนี้:
รหัส
เซลล์คลาสสาธารณะ
-
int cellContents; //เนื้อหาในวัตถุเซลล์
bool readerFlag = false; // สถานะสถานะ เมื่อเป็นจริงก็สามารถอ่านได้ เมื่อเป็นเท็จ แสดงว่ากำลังเขียน
สาธารณะ int ReadFromCell()
-
lock(this) // คีย์เวิร์ด Lock รับประกันอะไร โปรดอ่านบทนำก่อนหน้าเพื่อล็อค
-
if (!readerFlag)//หากไม่สามารถอ่านได้ในขณะนี้
-
พยายาม
-
//รอให้เรียกใช้เมธอด Monitor.Pulse() ในเมธอด WriteToCell
Monitor.รอ(นี่);
-
จับ (SynchronizationLockException e)
-
Console.WriteLine(e);
-
จับ (ThreadInterruptedException e)
-
Console.WriteLine(e);
-
-
Console.WriteLine("ใช้: {0}",cellContents);
readerFlag = เท็จ;
//รีเซ็ตการตั้งค่าสถานะ readerFlag เพื่อระบุว่าพฤติกรรมการบริโภคเสร็จสมบูรณ์แล้ว
Monitor.Pulse (นี้);
//แจ้งเมธอด WriteToCell() (เมธอดนี้ดำเนินการในเธรดอื่นและกำลังรออยู่)
-
ส่งคืนเนื้อหาเซลล์;
-
โมฆะสาธารณะ WriteToCell (int n)
-
ล็อค(นี้)
-
ถ้า (readerFlag)
-
พยายาม
-
Monitor.รอ(นี่);
-
จับ (SynchronizationLockException e)
-
//เมื่อวิธีการซิงโครไนซ์ (หมายถึงวิธีการของคลาส Monitor ยกเว้น Enter) จะถูกเรียกในพื้นที่โค้ดแบบอะซิงโครนัส
Console.WriteLine(e);
-
จับ (ThreadInterruptedException e)
-
//ยกเลิกเมื่อเธรดอยู่ในสถานะรอ
Console.WriteLine(e);
-
-
เนื้อหาเซลล์ = n;
Console.WriteLine("ผลิต: {0}",cellContents);
readerFlag = จริง;
Monitor.Pulse (นี้);
//แจ้งเมธอด ReadFromCell() ที่รออยู่ในเธรดอื่น
-
-
-
คลาสผู้ผลิต CellProd และคลาสผู้บริโภค CellCons ถูกกำหนดไว้ด้านล่าง ทั้งสองมีเมธอด ThreadRun() เพียงวิธีเดียว ดังนั้นออบเจ็กต์พร็อกซี ThreadStart ที่มอบให้กับเธรดในฟังก์ชัน Main() ทำหน้าที่เป็นทางเข้าสู่เธรด
CellProd คลาสสาธารณะ
-
เซลล์เซลล์ // วัตถุเซลล์กำลังทำงานอยู่
ปริมาณ int = 1; // จำนวนครั้งที่ผู้ผลิตผลิต เริ่มต้นเป็น 1
CellProd สาธารณะ (กล่องเซลล์ คำขอ int)
-
//คอนสตรัคเตอร์
เซลล์ = กล่อง;
ปริมาณ = คำขอ;
-
โมฆะสาธารณะ ThreadRun()
-
สำหรับ (int looper = 1; looper <= ปริมาณ; looper ++)
cell.WriteToCell(looper); // ผู้ผลิตเขียนข้อมูลไปยังวัตถุการดำเนินการ
-
-
CellCons คลาสสาธารณะ
-
เซลล์เซลล์;
ปริมาณ int = 1;
CellCons สาธารณะ (กล่องเซลล์, คำขอ int)
-
//คอนสตรัคเตอร์
เซลล์ = กล่อง;
ปริมาณ = คำขอ;
-
โมฆะสาธารณะ ThreadRun()
-
int valส่งคืน;
สำหรับ (int looper = 1; looper <= ปริมาณ; looper ++)
valReturned=cell.ReadFromCell();//Consumer อ่านข้อมูลจากวัตถุการดำเนินการ
-
-
จากนั้นในฟังก์ชัน Main() ของคลาส MonitorSample ด้านล่าง สิ่งที่เราต้องทำคือสร้างเธรดสองเธรดในฐานะโปรดิวเซอร์และคอนซูเมอร์ และใช้เมธอด CellProd.ThreadRun() และเมธอด CellCons.ThreadRun() เพื่อดำเนินการบนสิ่งเดียวกัน วัตถุเซลล์ทำงาน
รหัส
MonitorSample ระดับสาธารณะ
-
โมฆะสาธารณะคงที่ Main (String [] args)
-
int result = 0; //A แฟล็กบิต ถ้าเป็น 0 แสดงว่าไม่มีข้อผิดพลาดในโปรแกรม
เซลล์ เซลล์ = เซลล์ใหม่ ( );
// ข้อมูลต่อไปนี้ใช้เซลล์เพื่อเริ่มต้นคลาส CellProd และ CellCons เวลาในการผลิตและการบริโภคคือ 20 ครั้ง
CellProd prod = CellProd ใหม่ (เซลล์, 20);
ข้อเสียของ CellCons = CellCons ใหม่ (เซลล์ 20);
ผู้ผลิตเธรด = เธรดใหม่ (ThreadStart ใหม่ (prod.ThreadRun));
ผู้ใช้เธรด = เธรดใหม่ (ThreadStart ใหม่ (cons.ThreadRun));
//ทั้งเธรดผู้ผลิตและเธรดผู้บริโภคได้ถูกสร้างขึ้น แต่การดำเนินการยังไม่ได้เริ่มต้น
พยายาม
-
ผู้ผลิตเริ่มต้น ( );
ผู้บริโภคเริ่มต้น( );
โปรดิวเซอร์.เข้าร่วม( );
ผู้บริโภคเข้าร่วม( );
Console.ReadLine();
-
จับ (ThreadStateException จ)
-
//เมื่อเธรดไม่สามารถดำเนินการตามที่ร้องขอได้เนื่องจากสถานะของเธรด
Console.WriteLine(e);
ผลลัพธ์ = 1;
-
จับ (ThreadInterruptedException e)
-
//ยกเลิกเมื่อเธรดอยู่ในสถานะรอ
Console.WriteLine(e);
ผลลัพธ์ = 1;
-
//แม้ว่าฟังก์ชัน Main() จะไม่ส่งคืนค่า แต่คำสั่งต่อไปนี้สามารถส่งคืนผลการดำเนินการไปยังกระบวนการหลักได้
Environment.ExitCode = ผลลัพธ์;
-
-
ในรูทีนข้างต้น การซิงโครไนซ์ทำได้โดยการรอ Monitor.Pulse() ขั้นแรก ผู้ผลิตสร้างมูลค่า และในขณะเดียวกันผู้บริโภคก็อยู่ในสถานะรอจนกว่าจะได้รับ "พัลส์" จากผู้ผลิตเพื่อแจ้งให้ทราบว่าการผลิตเสร็จสมบูรณ์ หลังจากนั้น ผู้บริโภคจะเข้าสู่สถานะการบริโภค และผู้ผลิตเริ่มรอให้ผู้บริโภคดำเนินการให้เสร็จสิ้น "พัลส์" ที่ปล่อยออกมาจาก Monitor.Pulese() จะถูกเรียกหลังจากการดำเนินการ
ผลลัพธ์การดำเนินการนั้นง่ายมาก:
ผลิตผล: 1
การบริโภค: 1
ผลิตผล: 2
การบริโภค: 2
ผลิตผล: 3
การบริโภค: 3
-
-
ผลิตผล: 20
บริโภค: 20
ในความเป็นจริง ตัวอย่างง่ายๆ นี้ช่วยเราแก้ปัญหาใหญ่ที่อาจเกิดขึ้นในแอปพลิเคชันแบบมัลติเธรด ตราบใดที่คุณเข้าใจวิธีการพื้นฐานในการแก้ไขข้อขัดแย้งระหว่างเธรด คุณสามารถนำไปใช้กับโปรแกรมที่ซับซ้อนมากขึ้นได้อย่างง่ายดาย
-