จำเป็นต้องเข้าใจการใช้งานของ SemaphoreSlim


95

นี่คือรหัสที่ฉันมี แต่ฉันไม่เข้าใจว่าSemaphoreSlimกำลังทำอะไรอยู่

async Task WorkerMainAsync()
{
    SemaphoreSlim ss = new SemaphoreSlim(10);
    List<Task> trackedTasks = new List<Task>();
    while (DoMore())
    {
        await ss.WaitAsync();
        trackedTasks.Add(Task.Run(() =>
        {
            DoPollingThenWorkAsync();
            ss.Release();
        }));
    }
    await Task.WhenAll(trackedTasks);
}

void DoPollingThenWorkAsync()
{
    var msg = Poll();
    if (msg != null)
    {
        Thread.Sleep(2000); // process the long running CPU-bound job
    }
}

รอss.WaitAsync();และss.Release();ทำอะไร

ฉันเดาว่าถ้าฉันเรียกใช้ 50 เธรดในแต่ละครั้งให้เขียนโค้ดเช่นSemaphoreSlim ss = new SemaphoreSlim(10);นั้นมันจะถูกบังคับให้เรียกใช้เธรดที่ใช้งาน 10 เธรดในเวลานั้น

เมื่อหนึ่งใน 10 เธรดเสร็จสมบูรณ์เธรดอื่นจะเริ่มขึ้น ถ้าฉันไม่ถูกต้องช่วยฉันทำความเข้าใจกับสถานการณ์ตัวอย่าง

ทำไมจึงawaitจำเป็นต้องมีss.WaitAsync();? อะไรss.WaitAsync();ทำอย่างไร


3
สิ่งหนึ่งที่ควรทราบก็คือคุณควรใส่คำว่า "DoPollingThenWorkAsync ();" ใน "ลอง {DoPollingThenWorkAsync ();} ในที่สุด {ss.Release ();}" มิฉะนั้นข้อยกเว้นจะทำให้สัญญาณนั้นหยุดลงอย่างถาวร
Austin Salgat

ฉันรู้สึกแปลกนิดหน่อยที่เราได้รับและปล่อยสัญญาณภายนอก / ภายในงานตามลำดับ การย้าย "await ss.WaitAsync ()" ภายในงานจะสร้างความแตกต่างหรือไม่?
Shane Lu

คำตอบ:


77

ฉันเดาว่าถ้าฉันรันทีละ 50 เธรดแล้วโค้ดเช่น SemaphoreSlim ss = new SemaphoreSlim (10); จะบังคับให้รัน 10 เธรดที่ใช้งานอยู่ตลอดเวลา

ถูกต้อง; การใช้เซมาฟอร์ทำให้มั่นใจได้ว่าจะไม่มีคนงานมากกว่า 10 คนที่ทำงานนี้ในเวลาเดียวกัน

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

หมายเหตุด้านข้าง: DoPollingThenWorkAsyncไม่ควรมีAsyncpostfix เนื่องจากไม่ใช่แบบอะซิงโครนัสจริง แต่เป็นแบบซิงโครนัส เพียงแค่โทรDoPollingThenWork. จะช่วยลดความสับสนให้กับผู้อ่าน


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

@ คุณมีอะไรไม่ชัดเจนเกี่ยวกับเรื่องนี้? รหัสจะรอจนกว่าจะมีงานน้อยกว่า 10 งานที่กำลังทำงานอยู่ เมื่อมีก็เพิ่มอีก เมื่องานเสร็จสิ้นแสดงว่าเสร็จสิ้นแล้ว แค่นั้นแหละ.
Servy

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

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

3
@Servy นั่นเป็นส่วนหนึ่งของงานของตัวกำหนดตารางเวลางาน งาน! = เธรด Thread.Sleepในรหัสเดิมจะทำลายล้างการจัดตารางเวลางาน หากคุณไม่ได้เชื่อมต่อกับแกนหลักแสดงว่าคุณไม่ได้ซิงค์
Joseph Lennox

67

ในโรงเรียนอนุบาลบริเวณหัวมุมพวกเขาใช้ SemaphoreSlim เพื่อควบคุมจำนวนเด็กที่สามารถเล่นในห้อง PE ได้

พวกเขาวาดบนพื้นด้านนอกของห้อง 5 คู่ของรอยเท้า

เมื่อเด็ก ๆ มาถึงพวกเขาทิ้งรองเท้าไว้บนรอยเท้าฟรีและเข้าไปในห้อง

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

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

เมื่อครูอยู่ใกล้ ๆ เธอจะ "ปล่อย" รอยเท้าอีก 5 แถวที่อีกด้านหนึ่งของทางเดินเพื่อให้เด็ก ๆ อีก 5 คนสามารถเล่นในห้องได้ในเวลาเดียวกัน

มันก็มี "หลุมพราง" ของ SemaphoreSlim เหมือนกัน ...

หากเด็กเล่นจบและออกจากห้องโดยไม่เก็บรองเท้า (ไม่เรียก "การปลด") ช่องนั้นจะยังคงปิดกั้นแม้ว่าในทางทฤษฎีจะมีช่องว่าง เด็กมักจะถูกบอกเลิก

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

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


9
คำตอบประเภทนี้เป็นรายการโปรดของฉัน
My Stack Overfloweth

7

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

http://www.tomdupont.net/2016/03/how-to-release-semaphore-with-using.html

ฉันทำการแลกเปลี่ยน_isDisposed=trueและ_semaphore.Release()รอบ ๆ ในการกำจัดแม้ว่าในกรณีที่มีการเรียกหลายครั้ง

สิ่งสำคัญที่ควรทราบคือ SemaphoreSlim ไม่ใช่การล็อกแบบ reentrant ซึ่งหมายความว่าหากเธรดเดียวกันเรียกใช้ WaitAsync หลายครั้งจำนวนที่เซมาฟอร์จะลดลงทุกครั้ง ในระยะสั้น SemaphoreSlim ไม่ทราบเธรด

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


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