ไม่มี ConcurrentList <T> ใน. Net 4.0?


198

ฉันตื่นเต้นที่ได้เห็นSystem.Collections.Concurrentnamespace ใหม่ใน. Net 4.0 ค่อนข้างดี! ผมเคยเห็นConcurrentDictionary, ConcurrentQueue, ConcurrentStack, และConcurrentBagBlockingCollection

ConcurrentList<T>สิ่งหนึ่งที่น่าจะเป็นไปอย่างลึกลับหายไปเป็น ฉันต้องเขียนเอง (หรือเอามันออกจากเว็บ :))?

ฉันขาดอะไรบางอย่างชัดเจนที่นี่?



4
@RodrigoReis, ConcurrentBag <T> เป็นคอลเล็กชันที่ไม่เรียงลำดับขณะที่ List <T> ได้รับคำสั่ง
Adam Calvet Bohl

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

ใช้ล็อคแทน
Erik Bergstedt

มีไฟล์ชื่อ ThreadSafeList.cs ในซอร์สโค้ด dotnet ซึ่งมีลักษณะเหมือนโค้ดบางส่วนด้านล่าง มันใช้ ReaderWriterLockSlim ด้วยและพยายามหาสาเหตุที่ใช้แทนการล็อกแบบง่าย (obj)?
ลิน lamarre

คำตอบ:


166

ฉันลองดูสักครู่ (เช่น: บน GitHub ) การนำไปใช้ของฉันมีปัญหาบางอย่างซึ่งฉันจะไม่เข้าไปที่นี่ ให้ฉันบอกคุณที่สำคัญกว่านั้นคือสิ่งที่ฉันเรียนรู้

ประการแรกไม่มีทางที่คุณจะได้รับการนำไปใช้อย่างเต็มรูปแบบIList<T>ซึ่งไม่ล็อคและปลอดภัยสำหรับเธรด โดยเฉพาะอย่างยิ่งการแทรกและการลบแบบสุ่มจะไม่ทำงานเว้นแต่ว่าคุณจะลืมการเข้าถึงแบบสุ่ม O (1) (เช่นถ้าคุณ "โกง" และใช้รายการเชื่อมโยงบางประเภทแล้วปล่อยให้ดัชนีดูด)

สิ่งที่ผมคิดว่าอาจจะคุ้มค่าเป็นด้ายปลอดภัยเซตจำนวน จำกัดIList<T>: โดยเฉพาะอย่างยิ่งหนึ่งที่จะอนุญาตให้มีAddและให้สุ่มอ่านอย่างเดียวการเข้าถึงโดยดัชนี ( แต่ไม่มีInsert, RemoveAtฯลฯ และยังไม่มีการสุ่มเขียนเข้าถึง)

นี่คือเป้าหมายของฉันConcurrentList<T>การดำเนินงาน แต่เมื่อผมทดสอบประสิทธิภาพการทำงานในสถานการณ์แบบมัลติเธรดผมพบว่าเพิ่มเพียงแค่ตรงกันไปList<T>ได้เร็ว โดยพื้นฐานแล้วการเพิ่ม a List<T>นั้นเร็วมากแล้ว; ความซับซ้อนของขั้นตอนการคำนวณที่เกี่ยวข้องคือ miniscule (เพิ่มขึ้นดัชนีและกำหนดองค์ประกอบในอาร์เรย์ที่ว่ามันจริงๆ ) คุณจะต้องมีตันของการเขียนพร้อมกันเพื่อดูการจัดเรียงของการต่อสู้ล็อคเกี่ยวกับเรื่องนี้ใด ๆ และแม้กระทั่งตอนนี้ประสิทธิภาพโดยเฉลี่ยของการเขียนแต่ละครั้งจะยังคงเอาชนะการใช้งานแบบไม่มีล็อคแม้ว่าจะมีราคาแพงกว่าConcurrentList<T>ก็ตาม

ในเหตุการณ์ที่ค่อนข้างหายากที่อาร์เรย์ภายในของรายการต้องการปรับขนาดตัวเองคุณต้องจ่ายค่าใช้จ่ายเล็กน้อย ดังนั้นในท้ายที่สุดผมได้ข้อสรุปว่านี่เป็นหนึ่งในสถานการณ์เฉพาะที่ Add-เพียงConcurrentList<T>ประเภทคอลเลกชันจะทำให้ความรู้สึก: เมื่อคุณต้องการรับประกันค่าใช้จ่ายต่ำของการเพิ่มองค์ประกอบในทุกสายเดียว (ดังนั้นเมื่อเทียบกับเป้าหมายการปฏิบัติงานตัดจำหน่าย)

ชั้นเรียนมีประโยชน์ไม่มากเท่ากับที่คุณคิด


52
และถ้าคุณต้องการบางสิ่งที่คล้ายกับList<T>ที่ใช้การซิงค์แบบเก่ากับการติดตามบนจอมอนิเตอร์นั้นมีการSynchronizedCollection<T>ซ่อนอยู่ใน BCL: msdn.microsoft.com/en-us/library/ms668265.aspx
LukeH

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

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

2
@ เควิน: มันค่อนข้างง่ายที่จะสร้างConcurrentList<T>แบบที่ผู้อ่านรับประกันว่าจะเห็นสถานะที่สอดคล้องกันโดยไม่จำเป็นต้องล็อคใด ๆ ด้วยค่าใช้จ่ายที่เพิ่มขึ้นเล็กน้อย เมื่อรายการขยายจากขนาด 32 ถึง 64 ให้เก็บอาร์เรย์ size-32 ไว้และสร้างอาร์เรย์ size-64 ใหม่ เมื่อเพิ่มแต่ละรายการ 32 รายการถัดไปให้ใส่ไว้ในช่อง 32-63 ของอาร์เรย์ใหม่และคัดลอกรายการเก่าจากอาร์เรย์ขนาด 32 ไปยังรายการใหม่ จนกว่าจะมีการเพิ่มรายการที่ 64 ผู้อ่านจะดูในอาร์เรย์ขนาด -32 สำหรับรายการ 0-31 และในอาร์เรย์ขนาด -64 สำหรับรายการ 32-63
supercat

2
เมื่อเพิ่มรายการที่ 64 แล้วอาร์เรย์ขนาด -32 จะยังคงสามารถดึงรายการ 0-31 ได้ แต่ผู้อ่านไม่จำเป็นต้องใช้อีกต่อไป พวกเขาสามารถใช้อาร์เรย์ size-64 สำหรับรายการทั้งหมด 0-63 และอาร์เรย์ size-128 สำหรับรายการ 64-127 ค่าใช้จ่ายในการเลือกหนึ่งในสองของอาร์เรย์ที่จะใช้รวมทั้งอุปสรรคหน่วยความจำถ้าต้องการจะน้อยกว่าค่าใช้จ่ายของแม้แต่ล็อคล็อคเครื่องอ่าน - เขียนที่มีประสิทธิภาพที่สุดเท่าที่จะเป็นไปได้ การเขียนควรใช้ล็อก (โดยไม่มีการล็อคจะทำได้โดยเฉพาะอย่างยิ่งหากไม่มีใครคิดที่จะสร้างอินสแตนซ์วัตถุใหม่ที่มีการแทรกทุกครั้ง แต่ล็อคควรมีราคาถูก
supercat

38

คุณจะใช้ ConcurrentList เพื่ออะไร

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

  if (i < MyConcurrentList.Count)  
      x = MyConcurrentList[i]; 

ในภาพรวมจะไม่ปลอดภัยต่อเธรด

แทนที่จะสร้าง ConcurrentList ลองสร้างวิธีแก้ไขด้วยสิ่งที่มีอยู่ คลาสที่พบบ่อยที่สุดคือ ConcurrentBag และโดยเฉพาะ BlockingCollection


จุดดี. ยังคงสิ่งที่ฉันทำคือโลกีย์อีกเล็กน้อย ฉันแค่พยายามกำหนด ConcurrentBag <T> ให้เป็น IList <T> ฉันสามารถเปลี่ยนคุณสมบัติของฉันเป็น <t> IEnumerable ได้ แต่จากนั้นฉันไม่สามารถเพิ่มสิ่งต่าง ๆ ลงไปได้
อลัน

1
@Alan: ไม่มีทางที่จะใช้งานได้โดยไม่ต้องล็อครายการ เนื่องจากคุณสามารถใช้Monitorทำสิ่งนั้นได้อยู่แล้วจึงไม่มีเหตุผลสำหรับรายการที่เกิดขึ้นพร้อมกัน
Billy ONeal

6
@dcp - ใช่สิ่งนี้ไม่ปลอดภัยสำหรับเธรด ConcurrentDictionary มีวิธีพิเศษซึ่งทำสิ่งนี้ในการทำงานแบบปรมาณูเช่น AddOrUpdate, GetOrAdd, TryUpdate ฯลฯ พวกเขายังคงมีKeyเพราะบางครั้งคุณแค่อยากรู้ว่ากุญแจอยู่ตรงไหนโดยไม่ต้องแก้ไขพจนานุกรม (คิด HashSet)
Zarat

3
@dcp - containKey เป็น threadsafe ด้วยตัวเองตัวอย่างของคุณ (ไม่ใช่ containkey!) เพียงแค่มีสภาพการแข่งขันเพราะคุณทำการโทรครั้งที่สองขึ้นอยู่กับการตัดสินใจครั้งแรกซึ่งอาจ ณ จุดนั้นล้าสมัยแล้ว
Zarat

2
Henk ฉันไม่เห็นด้วย ฉันคิดว่ามีสถานการณ์ง่าย ๆ ที่เป็นประโยชน์อย่างมาก เธรดผู้ปฏิบัติงานเขียนในนั้นจะเธรด UI อ่านและปรับปรุงอินเทอร์เฟซ หากคุณต้องการเพิ่มรายการในแบบเรียงลำดับก็จะต้องมีการเข้าถึงแบบสุ่มเขียน คุณสามารถใช้กองซ้อนและมุมมองข้อมูลได้ แต่คุณจะต้องเก็บรักษา 2 ชุด :-(
Eric Ouellet

19

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

และสิ่งที่มีค่า: ฉันทำการทดสอบพื้นฐานของการเพิ่ม 10,000,000 รายการในรายการปกติและรายการพร้อมกันและผลลัพธ์คือ:

รายการเสร็จใน: 7793 มิลลิวินาที พร้อมกันเสร็จใน: 8064 มิลลิวินาที

public class ConcurrentList<T> : IList<T>, IDisposable
{
    #region Fields
    private readonly List<T> _list;
    private readonly ReaderWriterLockSlim _lock;
    #endregion

    #region Constructors
    public ConcurrentList()
    {
        this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        this._list = new List<T>();
    }

    public ConcurrentList(int capacity)
    {
        this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        this._list = new List<T>(capacity);
    }

    public ConcurrentList(IEnumerable<T> items)
    {
        this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        this._list = new List<T>(items);
    }
    #endregion

    #region Methods
    public void Add(T item)
    {
        try
        {
            this._lock.EnterWriteLock();
            this._list.Add(item);
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public void Insert(int index, T item)
    {
        try
        {
            this._lock.EnterWriteLock();
            this._list.Insert(index, item);
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public bool Remove(T item)
    {
        try
        {
            this._lock.EnterWriteLock();
            return this._list.Remove(item);
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public void RemoveAt(int index)
    {
        try
        {
            this._lock.EnterWriteLock();
            this._list.RemoveAt(index);
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public int IndexOf(T item)
    {
        try
        {
            this._lock.EnterReadLock();
            return this._list.IndexOf(item);
        }
        finally
        {
            this._lock.ExitReadLock();
        }
    }

    public void Clear()
    {
        try
        {
            this._lock.EnterWriteLock();
            this._list.Clear();
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public bool Contains(T item)
    {
        try
        {
            this._lock.EnterReadLock();
            return this._list.Contains(item);
        }
        finally
        {
            this._lock.ExitReadLock();
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        try
        {
            this._lock.EnterReadLock();
            this._list.CopyTo(array, arrayIndex);
        }
        finally
        {
            this._lock.ExitReadLock();
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new ConcurrentEnumerator<T>(this._list, this._lock);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return new ConcurrentEnumerator<T>(this._list, this._lock);
    }

    ~ConcurrentList()
    {
        this.Dispose(false);
    }

    public void Dispose()
    {
        this.Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
            GC.SuppressFinalize(this);

        this._lock.Dispose();
    }
    #endregion

    #region Properties
    public T this[int index]
    {
        get
        {
            try
            {
                this._lock.EnterReadLock();
                return this._list[index];
            }
            finally
            {
                this._lock.ExitReadLock();
            }
        }
        set
        {
            try
            {
                this._lock.EnterWriteLock();
                this._list[index] = value;
            }
            finally
            {
                this._lock.ExitWriteLock();
            }
        }
    }

    public int Count
    {
        get
        {
            try
            {
                this._lock.EnterReadLock();
                return this._list.Count;
            }
            finally
            {
                this._lock.ExitReadLock();
            }
        }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }
    #endregion
}

    public class ConcurrentEnumerator<T> : IEnumerator<T>
{
    #region Fields
    private readonly IEnumerator<T> _inner;
    private readonly ReaderWriterLockSlim _lock;
    #endregion

    #region Constructor
    public ConcurrentEnumerator(IEnumerable<T> inner, ReaderWriterLockSlim @lock)
    {
        this._lock = @lock;
        this._lock.EnterReadLock();
        this._inner = inner.GetEnumerator();
    }
    #endregion

    #region Methods
    public bool MoveNext()
    {
        return _inner.MoveNext();
    }

    public void Reset()
    {
        _inner.Reset();
    }

    public void Dispose()
    {
        this._lock.ExitReadLock();
    }
    #endregion

    #region Properties
    public T Current
    {
        get { return _inner.Current; }
    }

    object IEnumerator.Current
    {
        get { return _inner.Current; }
    }
    #endregion
}

5
ตกลงคำตอบเก่า แต่ยังคง: RemoveAt(int index)ไม่เคยด้ายปลอดภัยInsert(int index, T item)เป็นเพียงความปลอดภัยสำหรับดัชนี == 0, การกลับมาของIndexOf()ล้าสมัยทันที ฯลฯ this[int]ไม่ได้เริ่มต้นเกี่ยวกับ
Henk Holterman

2
และคุณไม่ต้องการและไม่ต้องการ ~ Finalizer ()
Henk Holterman

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

1
อินเทอร์เฟซที่ไม่พร้อมกันไม่เหมาะสำหรับการเข้าถึงพร้อมกัน var l = new ConcurrentList<string>(); /* ... */ l[0] += "asdf";เช่นต่อไปนี้จะไม่ได้เป็นอะตอม โดยทั่วไปคอมโบอ่าน - เขียนใด ๆ ที่จะนำคุณไปสู่ปัญหาลึกเมื่อทำพร้อมกัน นั่นคือเหตุผลที่โครงสร้างข้อมูลพร้อมกันโดยทั่วไปให้วิธีการเหล่านั้นเช่นConcurrentDictionary's AddOrGetฯลฯ NB คงที่ของคุณ (และซ้ำซ้อนเพราะสมาชิกมีการทำเครื่องหมายไว้แล้วดังกล่าวโดยขีดเส้นใต้) ซ้ำซ้อนของthis.clutters
Eugene Beresovsky

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

11

ConcurrentList(เป็นอาร์เรย์ที่ปรับขนาดได้ไม่ใช่รายการที่เชื่อมโยง) ไม่ใช่เรื่องง่ายที่จะเขียนด้วยการดำเนินการที่ไม่ปิดกั้น API ของมันแปลได้ไม่ดีนักกับเวอร์ชั่น "เกิดขึ้นพร้อมกัน"


12
มันไม่เพียง แต่ยากในการเขียน แต่ยังยากที่จะหาอินเทอร์เฟซที่มีประโยชน์
CodesInChaos

11

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

int catIndex = list.IndexOf("cat");
list.Insert(catIndex, "dog");

เอฟเฟกต์ที่ผู้เขียนดำเนินการหลังจากนั้นคือการแทรก "dog" ก่อน "cat" แต่ในสภาพแวดล้อมแบบมัลติเธรดสิ่งใด ๆ ที่อาจเกิดขึ้นกับรายการระหว่างโค้ดสองบรรทัดเหล่านั้น ตัวอย่างเช่นเธรดอื่นอาจทำlist.RemoveAt(0)โดยเลื่อนรายการทั้งหมดไปทางซ้าย แต่สำคัญมากcatIndexจะไม่เปลี่ยนแปลง ผลกระทบที่นี่คือการInsertผ่าตัดจะวาง "สุนัข" ตามหลังแมวไม่ใช่ก่อนหน้า

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

หากคุณคิดว่าคุณต้องการรายการที่เกิดขึ้นพร้อมกันมีเพียงสองอย่างที่เป็นไปได้:

  1. สิ่งที่คุณต้องการจริงๆคือ ConcurrentBag
  2. คุณจำเป็นต้องสร้างคอลเลกชันของคุณเองอาจนำไปใช้กับรายการและการควบคุมการทำงานพร้อมกันของคุณเอง

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


5

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

การใช้งานที่แสดงด้านล่างคือ

  • lockless
  • เร็วอย่างเห็นได้ชัดสำหรับการอ่านพร้อมกันแม้ในขณะที่การแก้ไขพร้อมกันกำลังดำเนินต่อไป - ไม่ว่าจะใช้เวลานานเท่าใด
  • เพราะ "สแนปช็อต" เป็นสิ่งที่เปลี่ยนไม่ได้ล็อคมิคแบบ atomicityกล่าวคือvar snap = _list; snap[snap.Count - 1];จะไม่ (ดียกเว้นรายการที่ว่างเปล่าของหลักสูตร) ​​และคุณยังได้รับการแจงนับข้อความที่ปลอดภัยด้วย semantics สแนปช็อตฟรี ..
  • ดำเนินการโดยทั่วไปใช้กับโครงสร้างข้อมูลใด ๆและชนิดของการปรับเปลี่ยนใด ๆ
  • ง่ายตายคือง่ายต่อการทดสอบตรวจแก้จุดบกพร่องตรวจสอบโดยการอ่านรหัส
  • ใช้งานได้ใน. Net 3.5

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

  1. โคลนโครงสร้าง
  2. ทำการแก้ไขบนโคลน
  3. แลกเปลี่ยนอะตอมในการอ้างอิงถึงโคลนดัดแปลง

รหัส

static class CopyOnWriteSwapper
{
    public static void Swap<T>(ref T obj, Func<T, T> cloner, Action<T> op)
        where T : class
    {
        while (true)
        {
            var objBefore = Volatile.Read(ref obj);
            var newObj = cloner(objBefore);
            op(newObj);
            if (Interlocked.CompareExchange(ref obj, newObj, objBefore) == objBefore)
                return;
        }
    }
}

การใช้

CopyOnWriteSwapper.Swap(ref _myList,
    orig => new List<string>(orig),
    clone => clone.Add("asdf"));

หากคุณต้องการประสิทธิภาพที่เพิ่มขึ้นมันจะช่วยแยกวิธีการเช่นสร้างวิธีการหนึ่งสำหรับการแก้ไขทุกประเภท (เพิ่ม, ลบ, ... ) ที่คุณต้องการและรหัสยากสำหรับพอยน์เตอร์ของฟังก์ชันclonerและopและ

NB # 1มันเป็นความรับผิดชอบของคุณเพื่อให้แน่ใจว่าไม่มีใครแก้ไขโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบ (สมมุติ) ไม่มีสิ่งใดที่เราสามารถทำได้ในการนำไปใช้ทั่วไปเพื่อป้องกันสิ่งนั้น แต่เมื่อเชี่ยวชาญList<T>คุณสามารถป้องกันการเปลี่ยนแปลงโดยใช้ List.AsReadOnly ()

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

NB # 3หากโครงสร้างข้อมูลของคุณมีขนาดใหญ่มากและคุณแก้ไขบ่อยวิธีการคัดลอกทั้งหมดเมื่อเขียนอาจเป็นสิ่งต้องห้ามทั้งในแง่ของปริมาณการใช้หน่วยความจำและค่าใช้จ่าย CPU ในการคัดลอกที่เกี่ยวข้อง ในกรณีดังกล่าวคุณอาจต้องการใช้คอลเล็กชันที่ไม่เปลี่ยนรูปแบบของ MS แทน


3

System.Collections.Generic.List<t>เธรดปลอดภัยแล้วสำหรับผู้อ่านหลายคน การพยายามทำให้เธรดปลอดภัยสำหรับนักเขียนหลาย ๆ คนจะไม่สมเหตุสมผล (สำหรับเหตุผลที่ Henk และ Stephen พูดถึงแล้ว)


คุณไม่เห็นสถานการณ์ที่ฉันอาจมี 5 เธรดเพิ่มลงในรายการหรือไม่ วิธีนี้คุณจะเห็นรายการสะสมเร็กคอร์ดแม้กระทั่งก่อนที่พวกเขาจะยุติ
อลัน

9
@Alan - นั่นจะเป็น ConcurrentQueue, ConcurrentStack หรือ ConcurrentBag เพื่อให้เป็นไปตามรายการ ConcurrentList คุณควรจัดเตรียม case-use ซึ่งคลาสที่มีอยู่ไม่เพียงพอ ฉันไม่เห็นสาเหตุที่ฉันต้องการเข้าถึงการจัดทำดัชนีเมื่อองค์ประกอบที่ดัชนีสามารถสุ่มเปลี่ยนผ่านการลบพร้อมกัน และสำหรับ "ล็อค" อ่านคุณสามารถใช้ภาพรวมของคลาสที่เกิดขึ้นพร้อมกันที่มีอยู่และวางไว้ในรายการ
Zarat

คุณพูดถูก - ฉันไม่ต้องการให้มีการเข้าถึงดัชนี โดยทั่วไปฉันใช้ IList <T> เป็นพร็อกซีสำหรับ IEnumerable ที่ฉันสามารถเพิ่มองค์ประกอบใหม่ได้ (T) นั่นคือที่มาของคำถามจริงๆ
Alan

@Alan: แล้วคุณต้องการคิวไม่ใช่รายการ
Billy ONeal

3
ฉันคิดว่าคุณผิด พูดว่า: ปลอดภัยสำหรับผู้อ่านหลายคนไม่ได้หมายความว่าคุณไม่สามารถเขียนได้ในเวลาเดียวกัน การเขียนจะหมายถึงการลบและคุณจะได้รับข้อผิดพลาดหากคุณลบในขณะที่วนซ้ำ
Eric Ouellet

2

บางคนเปล่งประกายคะแนนสินค้า (และความคิดของฉัน):

  • มันอาจดูเหมือนบ้าที่ไม่สามารถเข้าถึงตัวสุ่ม (ดัชนี) ได้ แต่สำหรับฉันมันก็ดูดี คุณต้องคิดว่ามีหลายวิธีในการรวบรวมหลายเธรดที่อาจล้มเหลวเช่นเครื่องมือสร้างดัชนีและลบ คุณยังสามารถกำหนดการกระทำที่ล้มเหลว (สำรอง) สำหรับการเขียนการเข้าถึงเช่น "ล้มเหลว" หรือเพียงแค่ "เพิ่มที่ส่วนท้าย"
  • ไม่ใช่เพราะเป็นคอลเล็กชันแบบมัลติเธรดที่จะถูกใช้ในบริบทแบบมัลติเธรดเสมอ หรือมันอาจถูกใช้โดยนักเขียนเพียงคนเดียวและผู้อ่านหนึ่งคน
  • อีกวิธีหนึ่งในการใช้ตัวจัดทำดัชนีในลักษณะที่ปลอดภัยอาจเป็นการห่อการกระทำไว้ในล็อกของคอลเลกชันโดยใช้รูทของมัน (ถ้าเปิดเผยต่อสาธารณะ)
  • สำหรับหลาย ๆ คนการทำให้รูทล็อคปรากฏให้เห็นเป็นเรื่องไม่สำคัญ "การปฏิบัติที่ดี" ฉันไม่แน่ใจ 100% เกี่ยวกับประเด็นนี้เพราะถ้ามันถูกซ่อนไว้คุณจะลบความยืดหยุ่นให้กับผู้ใช้ได้มาก เราต้องจำไว้เสมอว่าการเขียนโปรแกรมมัลติเธรดไม่ได้มีไว้สำหรับใคร เราไม่สามารถป้องกันการใช้งานทุกประเภทไม่ถูกต้อง
  • Microsoft จะต้องทำงานบางอย่างและกำหนดมาตรฐานใหม่บางอย่างเพื่อแนะนำการใช้งานที่เหมาะสมของคอลเลกชันแบบมัลติเธรด อันดับแรก IEnumerator ไม่ควรมี moveNext แต่ควรมี GetNext ที่คืนค่าจริงหรือเท็จและรับพารามิเตอร์ประเภท T (วิธีการวนซ้ำจะไม่ถูกบล็อกอีกต่อไป) นอกจากนี้ Microsoft ใช้ "ใช้" ภายใน foreach แล้ว แต่บางครั้งใช้ IEnumerator โดยตรงโดยไม่ต้องล้อมด้วย "ใช้" (ข้อผิดพลาดในมุมมองการรวบรวมและอาจอยู่ที่อื่น ๆ ) - การใช้ IEnumerator เป็นคำแนะนำจาก Microsoft ข้อผิดพลาดนี้จะลบโอกาสที่ดีสำหรับตัวทำซ้ำที่ปลอดภัย ... Iterator ที่ล็อกคอลเลกชันในตัวสร้างและปลดล็อกในวิธีการกำจัด - สำหรับวิธีการปิดกั้น foreach

นั่นไม่ใช่คำตอบ นี่เป็นเพียงความคิดเห็นที่ไม่สอดคล้องกับสถานที่เฉพาะเจาะจง

... ข้อสรุปของฉัน Microsoft ต้องทำการเปลี่ยนแปลงอย่างลึกซึ้งกับ "foreach" เพื่อให้การรวบรวมหลายเธรดใช้งานง่ายขึ้น นอกจากนี้ยังต้องปฏิบัติตามกฎการใช้งาน IEnumerator ของตัวเอง ก่อนหน้านั้นเราสามารถเขียน MultiThreadList ได้อย่างง่ายดายซึ่งจะใช้การบล็อกตัววนซ้ำ แต่จะไม่ทำตาม "IList" คุณจะต้องกำหนดอินเทอร์เฟซ "IListPersonnal" ของตัวเองซึ่งอาจล้มเหลวในการ "แทรก", "ลบ" และ accessor แบบสุ่ม (ตัวสร้างดัชนี) โดยไม่มีข้อยกเว้น แต่ใครจะต้องการใช้มันถ้ามันไม่ได้มาตรฐาน?


หนึ่งสามารถเขียนได้อย่างง่ายดายConcurrentOrderedBag<T>ซึ่งจะรวมถึงการใช้งานแบบอ่านอย่างเดียวIList<T>แต่ยังจะนำเสนอint Add(T value)วิธีการที่ปลอดภัยอย่างเต็มที่ด้าย ฉันไม่เห็นว่าทำไมต้องมีForEachการเปลี่ยนแปลงใด ๆ แม้ว่า Microsoft จะไม่พูดอย่างชัดเจน แต่การปฏิบัติของพวกเขาชี้ให้เห็นว่าเป็นที่ยอมรับได้อย่างสมบูรณ์ในIEnumerator<T>การระบุเนื้อหาของคอลเลกชันที่มีอยู่เมื่อมันถูกสร้างขึ้น ข้อยกเว้นแก้ไขคอลเลกชันจะต้องเฉพาะถ้าตัวแจงนับจะไม่สามารถรับประกันการดำเนินการปราศจากความผิดพลาด
supercat

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

สิ่งอื่นที่อาจเกิดขึ้นคือเมื่อคุณได้รับตัววนซ้ำคุณสามารถล็อกคอลเลกชันและเมื่อตัววนซ้ำของคุณถูกรวบรวม GC คุณสามารถปลดล็อกชุดสะสมได้ จากข้อมูลของ Microsfot พวกเขาได้ตรวจสอบว่า IEnumerable นั้นเป็น IDisposable หรือไม่และเรียก GC ถ้าเป็นเช่นนั้นในตอนท้ายของ ForEach ปัญหาหลักคือพวกเขายังใช้ IEnumerable ที่อื่นโดยไม่ต้องเรียก GC แล้วคุณไม่สามารถเชื่อถือได้ การมีอินเตอร์เฟส MT แบบใหม่ที่ชัดเจนสำหรับล็อคการเปิดใช้งาน IEnumerable จะช่วยแก้ปัญหาได้อย่างน้อยก็เป็นส่วนหนึ่งของมัน (มันจะไม่ป้องกันคนที่จะไม่เรียกมัน)
Eric Ouellet

มันเป็นรูปแบบที่แย่มากสำหรับGetEnumeratorวิธีการสาธารณะที่จะปล่อยให้คอลเลกชันถูกล็อคหลังจากที่มันกลับมา; การออกแบบดังกล่าวสามารถนำไปสู่การหยุดชะงักได้อย่างง่ายดาย IEnumerable<T>ให้ข้อบ่งชี้ว่าการแจงนับสามารถคาดว่าจะเสร็จสมบูรณ์แม้ว่าคอลเลกชันที่มีการแก้ไขไม่มี; วิธีที่ดีที่สุดสามารถทำได้คือเขียนวิธีการของตัวเองเพื่อให้พวกเขาทำเช่นนั้นและมีวิธีการที่ยอมรับIEnumerable<T>เอกสารข้อเท็จจริงที่ว่าจะปลอดภัยต่อIEnumerable<T>เธรดถ้าการแจงนับสนับสนุนเธรดที่ปลอดภัยเท่านั้น
supercat

สิ่งที่จะได้รับประโยชน์มากที่สุดจะได้รับหากIEnumerable<T>ได้รวมเป็น "ภาพรวม" IEnumerable<T>วิธีการที่มีประเภทผลตอบแทน คอลเลกชันที่ไม่เปลี่ยนรูปอาจกลับมาเองได้ คอลเลกชันที่ล้อมรอบได้หากไม่มีอะไรคัดลอกตัวเองไปยังList<T>หรือT[]และเรียกร้องGetEnumeratorให้ คอลเล็กชันที่ไม่มีขอบเขตสามารถนำไปใช้งานSnapshotได้และคอลเล็กชั่นที่ไม่สามารถโยนข้อยกเว้นได้โดยไม่ต้องพยายามเติมรายการที่มีเนื้อหา
supercat

1

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

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

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


1

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

public class CopyAndWriteList<T>
{
    public static List<T> Clear(List<T> list)
    {
        var a = new List<T>(list);
        a.Clear();
        return a;
    }

    public static List<T> Add(List<T> list, T item)
    {
        var a = new List<T>(list);
        a.Add(item);
        return a;
    }

    public static List<T> RemoveAt(List<T> list, int index)
    {
        var a = new List<T>(list);
        a.RemoveAt(index);
        return a;
    }

    public static List<T> Remove(List<T> list, T item)
    {
        var a = new List<T>(list);
        a.Remove(item);
        return a;
    }

}

การใช้งานตัวอย่าง: orders_BUY = CopyAndWriteList.Clear (orders_BUY);


แทนที่จะล็อคมันสร้างสำเนาของรายการปรับเปลี่ยนรายการและตั้งค่าการอ้างอิงไปยังรายการใหม่ ดังนั้นเธรดอื่น ๆ ที่วนซ้ำจะไม่ทำให้เกิดปัญหาใด ๆ
Rob The Quant

0

ผมดำเนินการอย่างใดอย่างหนึ่งที่คล้ายกับไบรอัน ของฉันแตกต่าง:

  • ฉันจัดการอาเรย์โดยตรง
  • ฉันไม่ได้เข้าล็อคภายในบล็อคลอง
  • ฉันใช้yield returnสำหรับสร้างตัวแจงนับ
  • ฉันสนับสนุนการเรียกซ้ำการล็อก อนุญาตให้อ่านจากรายการในระหว่างการทำซ้ำ
  • ฉันใช้ล็อคการอ่านที่อัปเกรดเป็นไปได้
  • DoSyncและGetSyncวิธีการที่อนุญาตให้มีการโต้ตอบแบบต่อเนื่องที่ต้องมีการเข้าถึงรายการ

รหัส :

public class ConcurrentList<T> : IList<T>, IDisposable
{
    private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    private int _count = 0;

    public int Count
    {
        get
        { 
            _lock.EnterReadLock();
            try
            {           
                return _count;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }
    }

    public int InternalArrayLength
    { 
        get
        { 
            _lock.EnterReadLock();
            try
            {           
                return _arr.Length;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }
    }

    private T[] _arr;

    public ConcurrentList(int initialCapacity)
    {
        _arr = new T[initialCapacity];
    }

    public ConcurrentList():this(4)
    { }

    public ConcurrentList(IEnumerable<T> items)
    {
        _arr = items.ToArray();
        _count = _arr.Length;
    }

    public void Add(T item)
    {
        _lock.EnterWriteLock();
        try
        {       
            var newCount = _count + 1;          
            EnsureCapacity(newCount);           
            _arr[_count] = item;
            _count = newCount;                  
        }
        finally
        {
            _lock.ExitWriteLock();
        }       
    }

    public void AddRange(IEnumerable<T> items)
    {
        if (items == null)
            throw new ArgumentNullException("items");

        _lock.EnterWriteLock();

        try
        {           
            var arr = items as T[] ?? items.ToArray();          
            var newCount = _count + arr.Length;
            EnsureCapacity(newCount);           
            Array.Copy(arr, 0, _arr, _count, arr.Length);       
            _count = newCount;
        }
        finally
        {
            _lock.ExitWriteLock();          
        }
    }

    private void EnsureCapacity(int capacity)
    {   
        if (_arr.Length >= capacity)
            return;

        int doubled;
        checked
        {
            try
            {           
                doubled = _arr.Length * 2;
            }
            catch (OverflowException)
            {
                doubled = int.MaxValue;
            }
        }

        var newLength = Math.Max(doubled, capacity);            
        Array.Resize(ref _arr, newLength);
    }

    public bool Remove(T item)
    {
        _lock.EnterUpgradeableReadLock();

        try
        {           
            var i = IndexOfInternal(item);

            if (i == -1)
                return false;

            _lock.EnterWriteLock();
            try
            {   
                RemoveAtInternal(i);
                return true;
            }
            finally
            {               
                _lock.ExitWriteLock();
            }
        }
        finally
        {           
            _lock.ExitUpgradeableReadLock();
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        _lock.EnterReadLock();

        try
        {    
            for (int i = 0; i < _count; i++)
                // deadlocking potential mitigated by lock recursion enforcement
                yield return _arr[i]; 
        }
        finally
        {           
            _lock.ExitReadLock();
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public int IndexOf(T item)
    {
        _lock.EnterReadLock();
        try
        {   
            return IndexOfInternal(item);
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    private int IndexOfInternal(T item)
    {
        return Array.FindIndex(_arr, 0, _count, x => x.Equals(item));
    }

    public void Insert(int index, T item)
    {
        _lock.EnterUpgradeableReadLock();

        try
        {                       
            if (index > _count)
                throw new ArgumentOutOfRangeException("index"); 

            _lock.EnterWriteLock();
            try
            {       
                var newCount = _count + 1;
                EnsureCapacity(newCount);

                // shift everything right by one, starting at index
                Array.Copy(_arr, index, _arr, index + 1, _count - index);

                // insert
                _arr[index] = item;     
                _count = newCount;
            }
            finally
            {           
                _lock.ExitWriteLock();
            }
        }
        finally
        {
            _lock.ExitUpgradeableReadLock();            
        }


    }

    public void RemoveAt(int index)
    {   
        _lock.EnterUpgradeableReadLock();
        try
        {   
            if (index >= _count)
                throw new ArgumentOutOfRangeException("index");

            _lock.EnterWriteLock();
            try
            {           
                RemoveAtInternal(index);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }
        finally
        {
            _lock.ExitUpgradeableReadLock();            
        }
    }

    private void RemoveAtInternal(int index)
    {           
        Array.Copy(_arr, index + 1, _arr, index, _count - index-1);
        _count--;

        // release last element
        Array.Clear(_arr, _count, 1);
    }

    public void Clear()
    {
        _lock.EnterWriteLock();
        try
        {        
            Array.Clear(_arr, 0, _count);
            _count = 0;
        }
        finally
        {           
            _lock.ExitWriteLock();
        }   
    }

    public bool Contains(T item)
    {
        _lock.EnterReadLock();
        try
        {   
            return IndexOfInternal(item) != -1;
        }
        finally
        {           
            _lock.ExitReadLock();
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {       
        _lock.EnterReadLock();
        try
        {           
            if(_count > array.Length - arrayIndex)
                throw new ArgumentException("Destination array was not long enough.");

            Array.Copy(_arr, 0, array, arrayIndex, _count);
        }
        finally
        {
            _lock.ExitReadLock();           
        }
    }

    public bool IsReadOnly
    {   
        get { return false; }
    }

    public T this[int index]
    {
        get
        {
            _lock.EnterReadLock();
            try
            {           
                if (index >= _count)
                    throw new ArgumentOutOfRangeException("index");

                return _arr[index]; 
            }
            finally
            {
                _lock.ExitReadLock();               
            }           
        }
        set
        {
            _lock.EnterUpgradeableReadLock();
            try
            {

                if (index >= _count)
                    throw new ArgumentOutOfRangeException("index");

                _lock.EnterWriteLock();
                try
                {                       
                    _arr[index] = value;
                }
                finally
                {
                    _lock.ExitWriteLock();              
                }
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }

        }
    }

    public void DoSync(Action<ConcurrentList<T>> action)
    {
        GetSync(l =>
        {
            action(l);
            return 0;
        });
    }

    public TResult GetSync<TResult>(Func<ConcurrentList<T>,TResult> func)
    {
        _lock.EnterWriteLock();
        try
        {           
            return func(this);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void Dispose()
    {   
        _lock.Dispose();
    }
}

จะเกิดอะไรขึ้นถ้าสองเธรดเข้าสู่จุดเริ่มต้นของtryบล็อกในRemoveหรือ seter ตัวทำดัชนีในเวลาเดียวกัน
James

@James ที่ดูเหมือนเป็นไปไม่ได้ อ่านคำพูดที่msdn.microsoft.com/en-us/library/... ใช้รหัสนี้คุณจะไม่ป้อนการล็อคครั้งที่ 2: gist.github.com/ronnieoverby/59b715c3676127a113c3
Ronnie Overby

@ Ronny Overby: น่าสนใจ ระบุว่าฉันสงสัยว่าสิ่งนี้จะทำงานได้ดีขึ้นมากถ้าคุณลบ UpgradableReadLock ออกจากฟังก์ชั่นทั้งหมดที่มีการดำเนินการเพียงอย่างเดียวในช่วงเวลาระหว่างล็อคการอ่านที่เลื่อนได้และล็อคการเขียน - ค่าใช้จ่ายในการล็อคชนิดใด ๆ กว่าการตรวจสอบเพื่อดูว่าพารามิเตอร์อยู่นอกช่วงที่เพิ่งทำการตรวจสอบภายในล็อคการเขียนน่าจะทำงานได้ดีขึ้น
James

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

1
ฉันต้องการบันทึกต่อไปโดยกล่าวว่าฉันรับรู้ว่าประโยชน์ของIListความหมายในสถานการณ์ที่เกิดขึ้นพร้อมกันนั้น จำกัด อย่างดีที่สุด ฉันเขียนโค้ดนี้ก่อนที่ฉันจะรู้ตัว ประสบการณ์ของฉันเหมือนกับนักเขียนของคำตอบที่ยอมรับ: ฉันลองกับสิ่งที่ฉันรู้เกี่ยวกับการซิงโครไนซ์และ IList <T> และฉันเรียนรู้บางสิ่งด้วยการทำเช่นนั้น
Ronnie Overby
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.