2010-02-13 31 views
5

Nếu tôi có một cái gì đó như thế này (giả):C hoạt động # danh sách multithreaded

class A 
{ 
    List<SomeClass> list; 

    private void clearList() 
    { 
     list = new List<SomeClass>(); 
    } 

    private void addElement() 
    { 
     list.Add(new SomeClass(...)); 
    } 
} 

là nó có thể là tôi chạy vào các vấn đề đa luồng (hoặc bất kỳ loại hành vi bất ngờ) khi cả hai chức năng được thực hiện song song?

Trường hợp sử dụng là danh sách lỗi, có thể bị xóa bất kỳ lúc nào (bằng cách chỉ định một danh sách mới, trống).

EDIT: giả định của tôi là

  • chỉ một thread thêm yếu tố
  • yếu tố quên được ổn (tức là tình trạng chủng tộc giữa thanh toán bù trừ và thêm một yếu tố mới), miễn là hoạt động rõ ràng thành công mà không cần vấn đề
  • NET 2,0

Trả lời

10

có hai khả năng cho các vấn đề ở đây:

  • Các mục mới được thêm vào có thể sẽ bị lãng quên ngay lập tức vì bạn đã xóa và tạo danh sách mới. Đó có phải là một vấn đề không? Về cơ bản, nếu AddElementClearList được gọi cùng một lúc, bạn có điều kiện chủng tộc: phần tử sẽ kết thúc trong danh sách mới hoặc trong phần tử cũ (bị lãng quên).
  • List<T> là không an toàn cho đa luồng đột biến, vì vậy nếu hai chủ đề khác nhau gọi AddElement cùng một lúc kết quả không đảm bảo

Cho rằng bạn đang truy cập vào một tài nguyên chia sẻ, tôi sẽ đích thân giữ một khóa trong khi truy cập nó. Bạn vẫn sẽ cần phải xem xét khả năng thanh toán bù trừ danh sách ngay trước/sau khi thêm một mục mặc dù.

EDIT: Cảm nhận của tôi về nó là không quan trọng nếu bạn chỉ thêm từ một thread là đã hơi mơ hồ, vì hai lý do:

  • Có thể (tôi nghĩ!) Mà bạn có thể sẽ cố gắng thêm vào một số List<T> chưa được xây dựng hoàn chỉnh. Tôi không chắc chắn, và mô hình bộ nhớ .NET 2.0 (trái ngược với mô hình trong đặc tả ECMA) có thể đủ mạnh để tránh điều đó, nhưng thật khó để nói.
  • Có thể là chuỗi thêm sẽ không "thấy" thay đổi đối với biến số list ngay lập tức và vẫn thêm vào danh sách cũ. Thật vậy, không có bất kỳ đồng bộ hóa, nó có thể nhìn thấy giá trị cũ mãi mãi

Khi bạn thêm "lặp lại trong GUI" vào trộn nó được thực sự khó khăn - bởi vì bạn không thể thay đổi danh sách trong khi bạn đang lặp lại. Giải pháp đơn giản nhất để điều này có lẽ để cung cấp một phương pháp mà trả về một sao chép của danh sách, và giao diện người dùng có thể an toàn lặp qua rằng:

class A 
{ 
    private List<SomeClass> list; 
    private readonly object listLock = new object(); 

    private void ClearList() 
    { 
     lock (listLock) 
     { 
      list = new List<SomeClass>(); 
     } 
    } 

    private void AddElement() 
    { 
     lock (listLock) 
     { 
      list.Add(new SomeClass(...)); 
     } 
    } 

    private List<SomeClass> CopyList() 
    { 
     lock (listLock) 
     { 
      return new List<SomeClass>(list); 
     } 
    } 

} 
+0

Chỉ có một chuỗi là thêm yếu tố, do đó, điểm thứ hai không phải là vấn đề đối với tôi. Và tôi đã nhận thức được điều kiện chủng tộc, nhưng điều đó không thực sự quan trọng đối với tôi - nhưng tôi không gọi 'Rõ ràng', thay vào đó tôi đang tạo một danh sách mới. Nó sẽ là một vấn đề để gọi 'Clear' và' Add' cùng một lúc (bạn nói 'List' không phải là thread-safe)? – AndiDog

+0

Xin lỗi, tôi có nghĩa là AddElement và ClearList. Chỉ cần tạo một danh sách mới sẽ không sao, và nếu bạn chỉ thêm từ một chuỗi duy nhất nên được tất cả các quyền. Đừng quên rằng có thể có thêm các biến chứng khi bạn đang đọc từ danh sách quá - sẽ chỉ xảy ra trong cùng một chủ đề đó là làm Add? –

+0

Không, một luồng GUI khác có thể lặp qua nó. Điều đó sẽ được thực hiện trong một vài tuần. Điều này có thể gây rắc rối nếu GUI sử dụng 'foreach (Element e trong instanceOfC.list) {...}' - nếu tôi gán một 'List' mới trong lần lặp này, GUI sẽ vẫn làm việc trên danh sách cũ, đúng không? – AndiDog

2

Có - nó có thể ,. Trong thực tế, nếu chúng thực sự được gọi cùng một lúc, nó rất có khả năng.

Ngoài ra, nó cũng có khả năng gây ra vấn đề nếu hai cuộc gọi riêng biệt để addElement xảy ra cùng một lúc.

Đối với loại đa luồng này, bạn thực sự cần một số loại khóa loại trừ lẫn nhau xung quanh danh sách, vì vậy chỉ có thể gọi một thao tác trên danh sách cơ bản tại một thời điểm.

Chiến lược khóa thô quanh việc này sẽ hữu ích. Một cái gì đó như:

class A 
{ 
    static object myLock = new object() 
    List<SomeClass> list; 

    private void clearList() 
    { 
     lock(myLock) 
     { 
      list = new List<SomeClass>(); 
     } 

    } 

    private void addElement() 
    { 
     lock(myLock) 
     { 
      list.Add(new SomeClass(...)); 
     } 
    } 
} 
1

Không phải là điều tốt khi chỉ tạo một Danh sách mới khi bạn muốn xóa nó.

Tôi giả sử bạn cũng được chỉ định danh sách trong hàm tạo nên bạn không chạy vào một ngoại lệ con trỏ null.

Nếu bạn rõ ràng và các yếu tố được thêm vào, chúng có thể được thêm vào danh sách cũ mà tôi cho là tốt? NHƯNG nếu hai phần tử được thêm vào cùng một lúc, bạn có thể gặp sự cố.

Nhìn vào Net 4 bộ sưu tập mới để xử lý các tác vụ đa luồng :)

ADDITION: Nhìn vào System.Collections.Concurrent namespace nếu bạn sử dụng Net 4. Ở đó bạn sẽ tìm thấy: System.Collections.Concurrent.ConcurrentBag<T> và nhiều bộ sưu tập đẹp khác :)

Bạn cũng nên lưu ý rằng khóa có thể giảm hiệu suất đáng kể nếu bạn không xem.

+0

Xin lỗi tôi đã không đề cập đến tôi đang sử dụng .NET 2.0 - do đó, có xây dựng các bộ sưu tập thread-an toàn trong .NET 2.0? Hay tôi phải sử dụng 'khóa'? – AndiDog

+0

Hhm, thay vì nói khóa vv rõ ràng tôi sẽ làm cho lớp của riêng tôi ala ThreadSafeList nơi các phương pháp bị khóa. Vì vậy, bạn không phải viết khóa nhiều lần (và có thể quên). –

+0

Tôi chỉ tìm thấy điều này: http://blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx nhưng tôi chưa xem mã kiểm tra nó trước khi sử dụng nó :) –

1

Nếu bạn sử dụng một phiên bản của lớp này trong nhiều chủ đề, vâng. bạn sẽ gặp rắc rối. Tất cả các bộ sưu tập trong khuôn khổ .Net (phiên bản 3.5 trở xuống) đều KHÔNG an toàn thread. Đặc biệt khi bạn bắt đầu thay đổi bộ sưu tập trong khi một chuỗi khác đang lật ngược nó.

Sử dụng khóa và đưa ra 'bộ sưu tập' bộ sưu tập trong môi trường đa luồng hoặc nếu bạn có thể sử dụng .Net 4.0, hãy sử dụng các bộ sưu tập đồng thời mới.

0

Rõ ràng từ việc chỉnh sửa cho câu hỏi của bạn rằng bạn không thực sự quan tâm đến thủ phạm thông thường ở đây - thực sự không có cuộc gọi đồng thời với các phương thức của cùng một đối tượng.

Về cơ bản bạn đang hỏi liệu có được phép gán tham chiếu đến danh sách của bạn trong khi nó đang được truy cập từ một chuỗi song song hay không.

Theo như tôi hiểu, nó vẫn có thể gây ra sự cố. Tất cả phụ thuộc vào cách phân công tham chiếu được thực hiện ở cấp phần cứng. Để chính xác hơn liệu hoạt động này có phải là nguyên tử hay không.

Tôi nghĩ rằng mỏng như nó vẫn còn là một cơ hội, đặc biệt là trong môi trường đa xử lý, quá trình sẽ bị tham chiếu bị hỏng vì nó chỉ được cập nhật một phần khi truy cập nó.

+0

Tôi không tin rằng sự khác biệt phần cứng/chuyển nhượng tài liệu tham khảo có thể gây ra rắc rối ở đây. Theo như tôi hiểu các thông số C# (Phần 5.5: http://msdn.microsoft.com/en-us/library/aa691278%28VS.71%29.aspx), nhiệm vụ tham chiếu phải là nguyên tử. – AndiDog

2

Bộ sưu tập trong .NET (tối đa 3.5) không an toàn hoặc không chặn (thực thi song song). Bạn nên thực hiện của bạn bằng cách bắt nguồn từ IList và sử dụng ReaderWriterLockSlim để thực hiện mọi hành động. Ví dụ: phương thức Thêm của bạn sẽ trông giống như sau:

public void Add(T item) 
    { 
     _readerWriterLockSlim.EnterWriteLock(); 
     try { _actualList.Add(item); } 
     finally { _readerWriterLockSlim.ExitWriteLock(); } 
    } 

Bạn phải biết một số thủ thuật đồng thời tại đây. Ví dụ bạn phải có một GetEnumerator trả về một cá thể mới là một IList; không phải danh sách thực tế.Nếu không bạn sẽ gặp rắc rối; mà nên hình như:

public IEnumerator<T> GetEnumerator() 
    { 
     List<T> localList; 

     _lock.EnterReadLock(); 
     try { localList= new List<T>(_actualList); } 
     finally { _lock.ExitReadLock(); } 

     foreach (T item in localList) yield return item; 
    } 

và:

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return ((IEnumerable<T>)this).GetEnumerator(); 
    } 

Lưu ý: Khi thực hiện bộ sưu tập thread-safe hoặc song song (và trong thực tế tất cả các lớp khác) KHÔNG xuất phát từ CLASS, nhưng giao diện! Bởi vì sẽ luôn có vấn đề liên quan đến cấu trúc bên trong của lớp đó hoặc một số phương thức không ảo và bạn phải ẩn chúng và vân vân. Nếu bạn phải làm điều này, hãy làm điều đó rất cẩn thận!

Các vấn đề liên quan