ตรวจสอบและล็อค


90

เมื่อใดจึงเหมาะสมที่จะใช้Monitorคลาสหรือlockคีย์เวิร์ดเพื่อความปลอดภัยของเธรดใน C #

แก้ไข: ดูเหมือนว่าจากคำตอบที่ผ่านมาlockนั้นสั้นสำหรับชุดของการโทรไปยังMonitorชั้นเรียน กุญแจล็อคสายสั้นมีไว้เพื่ออะไร? หรือชัดเจนกว่านั้น

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

อัปเดต

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

คำตอบ:


90

Eric Lippert พูดถึงเรื่องนี้ในบล็อกของเขา: ล็อคและข้อยกเว้นไม่ผสมกัน

รหัสเทียบเท่าแตกต่างกันระหว่าง C # 4.0 และเวอร์ชันก่อนหน้า


ใน C # 4.0 คือ:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    { body }
}
finally
{
    if (lockWasTaken) Monitor.Exit(temp);
}

มันขึ้นอยู่กับMonitor.Enterการตั้งค่าสถานะแบบอะตอมเมื่อมีการล็อค


และก่อนหน้านี้คือ:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

สิ่งนี้อาศัยไม่มีข้อยกเว้นในการโยนระหว่างMonitor.Enterและไฟล์try. ฉันคิดว่าในโค้ดดีบักเงื่อนไขนี้ถูกละเมิดเนื่องจากคอมไพเลอร์แทรก NOP ระหว่างพวกเขาและทำให้เธรดแท้งระหว่างสิ่งที่เป็นไปได้


ดังที่ฉันได้ระบุไว้ตัวอย่างแรกคือ C # 4 และอื่น ๆ คือเวอร์ชันก่อนหน้านี้ใช้
CodesInChaos

ตามหมายเหตุด้านข้าง C # ผ่าน CLR กล่าวถึงข้อแม้ของคีย์เวิร์ดล็อก: คุณมักต้องการทำบางอย่างเพื่อกู้คืนสถานะที่เสียหาย (ถ้ามี) ก่อนที่จะปลดล็อก เนื่องจากคีย์เวิร์ดล็อกไม่อนุญาตให้เราใส่สิ่งต่างๆลงในบล็อก catch เราจึงควรพิจารณาเขียน try-catch-ในที่สุดเวอร์ชันยาวสำหรับกิจวัตรที่ไม่สำคัญ
kizzx2

5
IMO คืนสถานะที่ใช้ร่วมกันจะตั้งฉากกับการล็อก / มัลติเธรด ดังนั้นจึงควรทำด้วย try-catch / สุดท้ายในlockบล็อก
CodesInChaos

2
@ kizzx2: รูปแบบดังกล่าวน่าจะดีอย่างยิ่งกับการล็อคตัวอ่านและนักเขียน หากมีข้อยกเว้นเกิดขึ้นภายในรหัสที่มีการล็อกผู้อ่านไม่มีเหตุผลที่จะคาดว่าทรัพยากรที่มีการป้องกันอาจเสียหายและไม่มีเหตุผลใดที่จะทำให้ไม่ถูกต้อง หากมีข้อยกเว้นเกิดขึ้นภายในตัวล็อคตัวเขียนและรหัสการจัดการข้อยกเว้นไม่ได้ระบุอย่างชัดแจ้งว่าสถานะของวัตถุที่มีการป้องกันได้รับการซ่อมแซมแล้วนั่นจะเป็นการชี้ให้เห็นว่าวัตถุอาจเสียหายและควรจะไม่ถูกต้อง IMHO ข้อยกเว้นที่ไม่คาดคิดไม่ควรทำให้โปรแกรมผิดพลาด แต่ควรทำให้สิ่งที่อาจเสียหายเป็นโมฆะ
supercat

2
@ArsenZahray คุณไม่จำเป็นต้องPulseล็อคง่ายๆ สิ่งสำคัญในสถานการณ์การทำงานแบบมัลติเธรดขั้นสูงบางอย่าง ฉันไม่เคยใช้Pulseโดยตรง
CodesInChaos

43

lockเป็นเพียงทางลัดสำหรับMonitor.Enterด้วยtry+ finallyและMonitor.Exit. ใช้คำสั่งล็อคเมื่อใดก็ตามที่เพียงพอ - หากคุณต้องการบางอย่างเช่น TryEnter คุณจะต้องใช้ Monitor


23

คำสั่งล็อคเทียบเท่ากับ:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

อย่างไรก็ตามโปรดทราบว่า Monitor ยังสามารถWait ()และPulse ()ซึ่งมักมีประโยชน์ในสถานการณ์มัลติเธรดที่ซับซ้อน

อัปเดต

อย่างไรก็ตามใน C # 4 มีการใช้งานที่แตกต่างกัน:

bool lockWasTaken = false;
var temp = obj;
try 
{
     Monitor.Enter(temp, ref lockWasTaken); 
     //your code
}
finally 
{ 
     if (lockWasTaken) 
             Monitor.Exit(temp); 
} 

ขอบคุณสำหรับ CodeInChaos สำหรับความคิดเห็นและลิงก์


ใน C # 4 คำสั่งล็อคถูกนำไปใช้แตกต่างกัน blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
CodesInChaos

16

Monitorมีความยืดหยุ่นมากขึ้น กรณีการใช้งานที่ฉันชอบในการใช้จอภาพคือเมื่อคุณไม่ต้องการรอถึงตาคุณและข้ามไป :

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}

6

อย่างที่คนอื่น ๆ บอกว่าlock"เทียบเท่า" กับ

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

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

แต่อีกครั้งสำหรับวิทยาศาสตร์สิ่งนี้ใช้ได้ดี:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

... และสิ่งนี้ไม่:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

ข้อผิดพลาด:

ข้อยกเว้นของชนิด 'System.Threading.SynchronizationLockException' เกิดขึ้นใน 70783sTUDIES.exe แต่ไม่ได้รับการจัดการในรหัสผู้ใช้

ข้อมูลเพิ่มเติม: วิธีการซิงโครไนซ์อ็อบเจ็กต์ถูกเรียกจากบล็อกโค้ดที่ไม่ซิงโครไนซ์

นี่เป็นเพราะMonitor.Exit(lockObject);จะดำเนินการlockObjectที่มีการเปลี่ยนแปลงเนื่องจากstringsไม่เปลี่ยนรูปจากนั้นคุณจะเรียกใช้จากบล็อกโค้ดที่ไม่ซิงโครไนซ์ .. แต่อย่างไรก็ตาม นี่เป็นเพียงเรื่องสนุก ๆ


"นี่เป็นเพราะ Monitor.Exit (lockObject); จะดำเนินการกับ lockObject" แล้วล็อคไม่มีอะไรกับวัตถุ? ล็อคทำงานอย่างไร?
Yugo Amaryl

@YugoAmaryl ฉันคิดว่าเป็นเพราะคำสั่งล็อคโปรดทราบว่าผ่านการอ้างอิงก่อนแล้วจึงใช้แทนการใช้การอ้างอิงที่เปลี่ยนแปลงเช่น:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.

3

ทั้งสองอย่างคือสิ่งเดียวกัน ล็อคเป็นคำหลักที่คมชัดและใช้คลาส Monitor

http://msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx


3
ดูที่msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx "ในความเป็นจริงคีย์เวิร์ดล็อกถูกนำไปใช้กับคลาส Monitor ตัวอย่างเช่น"
RobertoBr

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

3

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

ล็อคเป็นทางลัดและเป็นตัวเลือกสำหรับการใช้งานพื้นฐาน

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


1

ล็อค ล็อคคำหลักเพื่อให้แน่ใจว่าด้ายรันชิ้นส่วนของรหัสที่ครั้งหนึ่ง

ล็อค (lockObject)

        {
        //   Body
        }

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

หากเธรดอื่นพยายามป้อนรหัสที่ล็อกไว้เธรดจะรอบล็อกจนกว่าอ็อบเจ็กต์จะถูกปล่อยออกมา

Monitor จอภาพเป็นคลาสแบบคงที่และเป็นของ System.Threading namespace

มันมีการล็อคพิเศษบนวัตถุเพื่อให้เธรดเดียวเท่านั้นที่สามารถเข้าสู่ส่วนวิกฤตในช่วงเวลาใดก็ได้

ความแตกต่างระหว่าง Monitor และ Lock ใน C #

การล็อคเป็นทางลัดสำหรับการตรวจสอบป้อนด้วยการลองและสุดท้าย ล็อคจับลองและสุดท้ายบล็อกภายใน Lock = Monitor + ลองในที่สุด

หากคุณต้องการควบคุมได้มากขึ้นที่จะใช้โซลูชั่นมัลติเธรดขั้นสูงโดยใช้TryEnter() Wait(), Pulse()และPulseAll()วิธีการแล้วระดับจอภาพเป็นตัวเลือกของคุณ

C # Monitor.wait(): เธรดรอเธรดอื่นแจ้ง

Monitor.pulse(): เธรดแจ้งไปยังเธรดอื่น

Monitor.pulseAll(): เธรดแจ้งเธรดอื่น ๆ ทั้งหมดภายในกระบวนการ


0

นอกเหนือจากคำอธิบายข้างต้นทั้งหมดแล้วการล็อกคือคำสั่ง C # ในขณะที่ Monitor เป็นคลาสของ. NET ที่อยู่ใน System.Threading namespace

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