ข้อความสั่งคืนควรอยู่ภายในหรือด้านนอกล็อคหรือไม่?


142

ฉันเพิ่งรู้ว่าในบางแห่งในรหัสของฉันฉันมีคำสั่งส่งคืนภายในล็อคและบางครั้งข้างนอก อันไหนดีที่สุด?

1)

void example()
{
    lock (mutex)
    {
    //...
    }
    return myData;
}

2)

void example()
{
    lock (mutex)
    {
    //...
    return myData;
    }

}

ฉันควรใช้อันไหนดี


วิธีการเกี่ยวกับการยิง Reflector และทำการเปรียบเทียบ IL ;-)
Pop Catalin

6
@Pop: เสร็จแล้ว - ไม่ดีไปกว่าในแง่ของ IL - ใช้กับรูปแบบ C # เท่านั้น
Marc Gravell

1
น่าสนใจมากว้าวฉันเรียนรู้วันนี้!
Pokus

คำตอบ:


192

เป็นหลักซึ่งเคยทำให้รหัสง่ายขึ้น จุดทางออกเดียวเป็นอุดมคติที่ดี แต่ฉันจะไม่งอรหัสออกจากรูปร่างเพียงเพื่อให้บรรลุ ... และหากทางเลือกคือการประกาศตัวแปรท้องถิ่น (นอกล็อค) เริ่มต้นมัน (ภายในล็อค) และ จากนั้นส่งคืน (นอกล็อค) จากนั้นฉันจะบอกว่า "return foo" ภายในล็อคนั้นง่ายกว่ามาก

หากต้องการแสดงความแตกต่างใน IL ให้ใช้รหัส:

static class Program
{
    static void Main() { }

    static readonly object sync = new object();

    static int GetValue() { return 5; }

    static int ReturnInside()
    {
        lock (sync)
        {
            return GetValue();
        }
    }

    static int ReturnOutside()
    {
        int val;
        lock (sync)
        {
            val = GetValue();
        }
        return val;
    }
}

(โปรดทราบว่าฉันขอยืนยันว่าReturnInsideเป็นบิตที่เรียบง่าย / สะอาดกว่าของ C #)

และดูที่ IL (โหมดการเปิดตัว ฯลฯ ):

.method private hidebysig static int32 ReturnInside() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 CS$1$0000,
        [1] object CS$2$0001)
    L_0000: ldsfld object Program::sync
    L_0005: dup 
    L_0006: stloc.1 
    L_0007: call void [mscorlib]System.Threading.Monitor::Enter(object)
    L_000c: call int32 Program::GetValue()
    L_0011: stloc.0 
    L_0012: leave.s L_001b
    L_0014: ldloc.1 
    L_0015: call void [mscorlib]System.Threading.Monitor::Exit(object)
    L_001a: endfinally 
    L_001b: ldloc.0 
    L_001c: ret 
    .try L_000c to L_0014 finally handler L_0014 to L_001b
} 

method private hidebysig static int32 ReturnOutside() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 val,
        [1] object CS$2$0000)
    L_0000: ldsfld object Program::sync
    L_0005: dup 
    L_0006: stloc.1 
    L_0007: call void [mscorlib]System.Threading.Monitor::Enter(object)
    L_000c: call int32 Program::GetValue()
    L_0011: stloc.0 
    L_0012: leave.s L_001b
    L_0014: ldloc.1 
    L_0015: call void [mscorlib]System.Threading.Monitor::Exit(object)
    L_001a: endfinally 
    L_001b: ldloc.0 
    L_001c: ret 
    .try L_000c to L_0014 finally handler L_0014 to L_001b
}

ดังนั้นในระดับ IL พวกเขา [ให้หรือรับชื่อ] เหมือนกัน (ฉันเรียนรู้บางสิ่ง ;-p) ดังนั้นการเปรียบเทียบที่สมเหตุสมผลเพียงอย่างเดียวคือกฎหมาย (ส่วนตัวสูง) ของรูปแบบการเข้ารหัสในท้องถิ่น ... ฉันชอบReturnInsideความเรียบง่าย แต่ฉันก็ไม่ได้รู้สึกตื่นเต้นเหมือนกัน


15
ฉันใช้. NET Reflector (ฟรีและยอดเยี่ยม) ของ Red Gate (เคยเป็น:. NET Reflector ของ Lutz Roeder's) แต่ ILDASM ก็ทำเช่นเดียวกัน
Marc Gravell

1
หนึ่งในแง่มุมที่ทรงพลังที่สุดของ Reflector คือคุณสามารถแยก IL กับภาษาที่คุณต้องการได้ (C #, VB, Delphi, MC ++, Chrome, ฯลฯ )
Marc Gravell

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

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

1
@RaheelKhan ไม่ไม่มี; พวกเขาก็เหมือน ๆ กัน. ในระดับ IL คุณจะไม่สามารถ retอยู่ภายใน.tryภูมิภาคได้
Marc Gravell

42

มันไม่ได้สร้างความแตกต่างใด ๆ พวกเขาทั้งคู่แปลเป็นสิ่งเดียวกันโดยคอมไพเลอร์

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

T myData;
Monitor.Enter(mutex)
try
{
    myData= // something
}
finally
{
    Monitor.Exit(mutex);
}

return myData;

1
นั่นเป็นเรื่องจริงของการลอง / ในที่สุด - อย่างไรก็ตามการกลับมาด้านนอกล็อคยังคงต้องใช้คนในพื้นที่ที่ไม่สามารถปรับให้เหมาะสมได้ - และต้องใช้รหัสมากขึ้น ...
Marc Gravell

3
คุณไม่สามารถกลับจากการลองบล็อก มันจะต้องลงท้ายด้วยรหัส "ople" ดังนั้น CIL ที่ปล่อยออกมาควรจะเหมือนกันทั้งสองกรณี
Greg Beech

3
คุณถูกต้อง - ฉันเพิ่งดู IL (ดูโพสต์ที่อัปเดต) ฉันได้เรียนรู้บางสิ่ง ;-p
Marc Gravell

2
เจ๋งน่าเสียดายที่ฉันเรียนรู้จากชั่วโมงที่เจ็บปวดพยายามที่จะปล่อย. op-codes ในบล็อกลองและมี CLR ปฏิเสธที่จะโหลดวิธีการแบบไดนามิกของฉัน :-(
Greg Beech

ฉันสามารถเกี่ยวข้อง; ฉันทำ Reflection ในปริมาณที่พอเหมาะยอมรับ แต่ฉันขี้เกียจ ถ้าฉันไม่แน่ใจเกี่ยวกับบางอย่างฉันจะเขียนรหัสตัวแทนใน C # แล้วดู IL แต่น่าแปลกใจที่คุณเริ่มคิดในแง่ของ IL ได้เร็วเพียงใด (เช่นการเรียงลำดับสแต็ก)
Marc Gravell

28

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


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

4
ฉันประหลาดใจที่คำตอบอื่น ๆ อย่าพูดถึงเรื่องนี้
Akshat Agarwal

5
ในตัวอย่างนี้พวกเขากำลังพูดถึงการใช้ตัวแปรสแต็คเพื่อเก็บค่าส่งคืนคือเฉพาะคำสั่งส่งคืนนอกล็อคและแน่นอนการประกาศตัวแปร อีกเธรดหนึ่งควรมีอีกสแต็คดังนั้นจึงไม่สามารถสร้างความเสียหายใด ๆ ได้ใช่ไหม?
Guillermo Ruffino

3
ฉันไม่คิดว่านี่เป็นจุดที่ถูกต้องเนื่องจากเธรดอื่นสามารถอัปเดตค่าระหว่างการเรียกคืนและการกำหนดค่าส่งคืนที่แท้จริงให้กับตัวแปรบนเธรดหลัก มูลค่าที่ส่งคืนไม่สามารถเปลี่ยนแปลงหรือรับรองความสอดคล้องกับมูลค่าที่แท้จริงในปัจจุบันด้วยวิธีใดวิธีหนึ่ง ขวา?
UrošJoksimović

คำตอบนี้ไม่ถูกต้อง เธรดอื่นไม่สามารถเปลี่ยนตัวแปรโลคัลได้ ตัวแปรโลคัลถูกเก็บในสแต็กและแต่ละเธรดมีสแต็กของตัวเอง Btw ขนาดเริ่มต้นของสแต็คของเธรดเป็น1 เมกะไบต์
Theodor Zoulias

5

มันขึ้นอยู่กับ,

ฉันจะไปต่อต้านข้าวที่นี่ ฉันมักจะกลับมาภายในล็อค

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

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

void example() { 
    int myData;
    lock (foo) { 
        myData = ...;
    }
    return myData
}

เมื่อเทียบกับ

void example() { 
    lock (foo) {
        return ...;
    }
}

ฉันพบว่ากรณีที่ 2 อ่านง่ายกว่ามากและยากที่จะพลาดโดยเฉพาะอย่างยิ่งสำหรับตัวอย่างสั้น ๆ


4

ถ้าคิดว่าล็อคด้านนอกดูดีขึ้น แต่ระวังถ้าคุณเปลี่ยนรหัสเป็น:

return f(...)

ถ้าต้องเรียกใช้ f () ด้วยการล็อคที่ล็อคไว้มันจะต้องอยู่ภายในล็อคอย่างเห็นได้ชัดเช่นการกลับมาที่การล็อคภายในเพื่อความสอดคล้องจะทำให้รู้สึกได้


1

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


0

เพื่อให้ง่ายขึ้นสำหรับผู้พัฒนาเพื่อนในการอ่านรหัสฉันอยากจะแนะนำทางเลือกแรก


0

lock() return <expression> งบเสมอ:

1) ใส่ล็อค

2) สร้าง local (thread-safe) store สำหรับค่าชนิดที่ระบุ

3) เติมร้านค้าด้วยค่าที่ส่งคืนโดย <expression> ,

4) ล็อคทางออก

5) คืนร้านค้า

หมายความว่าค่านั้นส่งคืนจากคำสั่งล็อกเสมอ "สุก" ก่อนกลับ

ไม่ต้องกังวลlock() returnอย่าฟังใครเลยที่นี่))


-2

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

เพื่อสรุปและเติมเต็มคำตอบที่มีอยู่:

  • ยอมรับคำตอบที่แสดงให้เห็นว่าโดยไม่คำนึงถึงรูปแบบที่คุณเลือกไวยากรณ์ของคุณในC #รหัสในรหัส IL - และดังนั้นที่รันไทม์ - The returnไม่เกิดขึ้นจนกว่าหลังจากปลดล็อคตัวล็อค

    • แม้ว่าการวางreturn ภายในlockบล็อกจึงพูดอย่างเคร่งครัดแพ้ภัยการไหลของการควบคุม[1]มันเป็นไวยากรณ์ที่สะดวกในการที่จะ obviates ความจำเป็นในการจัดเก็บค่าตอบแทนใน aux ตัวแปรท้องถิ่น (ประกาศนอกบล็อกเพื่อที่จะสามารถนำมาใช้กับreturnนอกบล็อก) - ดูคำตอบของเอ็ดเวิร์ด KMETT
  • แยกกัน - และด้านนี้เป็นเรื่องที่เกิดขึ้นกับคำถาม แต่ก็อาจเป็นที่สนใจ ( คำตอบของ Ricardo Villamilพยายามที่จะกล่าวถึง แต่ไม่ถูกต้องฉันคิดว่า) - การรวมlockคำสั่งกับreturnคำสั่ง - กล่าวคือการได้รับคุณค่าreturnในบล็อกที่ป้องกัน การเข้าถึงพร้อมกัน - "ปกป้อง" ค่าที่ส่งคืนในขอบเขตของผู้โทรอย่างมีความหมายเท่านั้นหากไม่จำเป็นต้องได้รับการปกป้องเมื่อได้รับจริงซึ่งจะใช้ในสถานการณ์ต่อไปนี้:

    • หากค่าที่ส่งคืนเป็นองค์ประกอบจากคอลเลกชันที่ต้องการการป้องกันในแง่ของการเพิ่มและลบองค์ประกอบเท่านั้นไม่ใช่ในแง่ของการแก้ไของค์ประกอบเองและ / หรือ ...

    • ... ถ้าค่าถูกส่งกลับเป็นตัวอย่างของประเภทค่าหรือสตริง

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


[1] โอดอร์ Zouliasชี้ให้เห็นว่านั่นคือในทางเทคนิคยังเป็นจริงสำหรับการวางreturnภายในtry, catch, using, if, while, for... งบ; อย่างไรก็ตามมันเป็นจุดประสงค์เฉพาะของlockข้อความที่มีแนวโน้มว่าจะเชิญให้ตรวจสอบการไหลของการควบคุมที่แท้จริงดังที่เห็นได้จากคำถามนี้ที่ถูกถามและได้รับความสนใจมาก

[2] การเข้าถึงอินสแตนซ์ประเภทค่าจะสร้างสำเนาของเธรดแบบโลคัลและแบบ on-the-stack แม้ว่าสตริงนั้นเป็นอินสแตนซ์ชนิดอ้างอิงทางเทคนิค แต่สตริงเหล่านั้นจะทำงานได้อย่างมีประสิทธิภาพเช่นในประเภทอินสแตนซ์


เกี่ยวกับสถานะปัจจุบันของคำตอบของคุณ (แก้ไข 13) คุณยังคงคาดเดาเกี่ยวกับสาเหตุของการมีอยู่ของlockและได้รับความหมายจากการวางคำสั่งส่งคืน การสนทนาใดที่ไม่เกี่ยวข้องกับคำถามนี้ IMHO นอกจากนี้ฉันพบการใช้งาน"ผิดปกติ"ค่อนข้างรบกวน ถ้ากลับจากการlockแจ้งข้อมูลเท็จการไหลของควบคุมแล้วเดียวกันอาจจะกล่าวว่าสำหรับกลับมาจากtry, catch, using, if, while, forและโครงสร้างอื่น ๆ ของภาษา มันเหมือนกับการบอกว่า C # เต็มไปด้วยการบิดเบือนความจริงของการควบคุมกระแส พระเยซู ...
Theodor Zoulias

"มันก็เหมือนกับการพูดว่า C # นั้นเต็มไปด้วยการบิดเบือนความจริงของการควบคุม" - นั่นคือความจริงทางเทคนิคและคำว่า "การบิดเบือนความจริง" เป็นเพียงการตัดสินมูลค่าหากคุณเลือกที่จะทำเช่นนั้น ด้วยtry, if... โดยส่วนตัวฉันมักจะไม่คิดถึงแม้แต่เรื่องนี้ แต่ในบริบทของlockเฉพาะคำถามที่เกิดขึ้นกับฉัน - และถ้ามันไม่เกิดขึ้นกับคนอื่นเช่นกันคำถามนี้จะไม่ถูกถาม และคำตอบที่ได้รับการยอมรับจะไม่ได้ไปไกลขนาดใหญ่ในการตรวจสอบพฤติกรรมที่แท้จริง
mklement0
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.