2012-10-19 39 views
6

Tôi có lớp tĩnh này chứa một biến tĩnh (một int đơn giản). Tôi đã thực hiện một lock() trong phương pháp Run() của chủ đề, vì vậy không đề khác có thể truy cập vào lớp này đồng thời, nhưng biến vẫn đi điên, hiển thị bản sao, vô cùng giá trị cao vvCách tạo một biến tĩnh an toàn

Đây là lớp học:

public static class ExplorationManager 
{ 
    public static int Counter = 0; 

    public static void ExplorerMaker(List<int[]> validPaths, List<string> myParents, string[,] myExplorationMap, List<int[]> myPositions) 
    { 
     foreach (var thread in validPaths.Select 
     (path => new Explorer(myParents, path, myExplorationMap, myPositions)). 
     Select(explorer => new Thread(explorer.Explore))) 
      { 
       thread.Name = "Thread of " + Counter + " generation"; 
       Counter++; 
       thread.Start(); 
    } 
} 

}

có cách nào để làm cho biến này "nhiều hơn" thread-an toàn không?

+2

_goes điên, hiển thị trùng lặp, giá trị điên rồ cao_ - không thực sự giải thích được từ mã này. –

Trả lời

25

Có ít nhất 2 vấn đề bạn cần giải quyết để tăng tính an toàn cho loại này.

Cách thứ nhất là tạo Counterprivate. Ở dạng hiện tại, biến này là 100% công khai và nó có thể bị biến đổi bởi bất kỳ đoạn mã nào trong ứng dụng. Hôm nay nó có thể an toàn nhưng không có gì bảo vệ bạn khỏi việc phạm sai lầm vào ngày mai. Nếu bạn vẫn muốn phần khác của mã để có thể đọc bất động sản sau đó sử dụng một accessor

private static int m_counter; 
public static int Counter { 
    get { return m_counter; } 
} 

Vấn đề thứ hai là ++ không phải là một hoạt động an toàn trên một vị trí được chia sẻ giữa các chủ đề.Nó mở rộng ra các mã sau

Counter = Counter + 1; 

Đó là trong thực tế làm

  1. Counter tải
  2. tải 1
  3. thêm
  4. cửa hàng Counter

Một thread có thể bị gián đoạn hầu như bất kỳ lúc nào. Nếu một luồng bị gián đoạn ở bước 1, 2 hoặc 3 và một luồng khác thực hiện đầy đủ trình tự thì bạn sẽ kết thúc việc thêm/lưu trữ các giá trị cũ. Đây là lý do tại sao ++ không an toàn. Cách an toàn để tăng giá trị chia sẻ giữa các chủ đề là sử dụng Interlocked.Increment. Nó được thiết kế chính xác cho mục đích này

Interlocked.Increment(ref m_counter); 
+0

Nếu cách này không hiệu quả, vấn đề là với mã của tôi? Hay có cách nào khác để đạt được điều này? –

5

Bạn cần sử dụng lock xung quanh tất cả lần đọc/ghi của biến tĩnh của bạn. Một cái gì đó như:

public static readonly object CounterLock = new object(); 

... 
lock (CounterLock) 
{ 
    Counter++; 
} 
... 

Điểm có ích là tất cả đọc/viết phải được bảo vệ bởi khóa - nó không đủ để bảo vệ một nơi duy nhất bởi vì khi đó bài làm lần đọc hoặc viết vẫn có thể thực hiện thay đổi khi một khóa ở nơi khác có hiệu lực.

Khóa bảo vệ vùng mã, không phải biến, đó là lý do tại sao bạn cần khóa ở mọi nơi, nơi bạn truy cập biến được chia sẻ.

Lưu ý rằng bạn không thể khóa trên biến số Counter - bạn cần một phiên bản của loại tham chiếu dưới dạng khóa, không phải loại giá trị. Đây là lý do tại sao tôi sử dụng object làm loại khóa (câu trả lời khác cũng làm như vậy).

+0

Tôi không biết điều đó. Tôi nghĩ rằng việc bảo vệ quyền truy cập vào lớp từ phương thức 'Run()', thực sự bảo vệ nó. Cảm ơn. –

+0

CounterLock phải là 'readonly' –

+0

@ XaweryWiśniowiecki Có, bạn nói đúng. Tôi sẽ cập nhật câu trả lời. Cảm ơn bạn. – xxbbcc

1

Bạn có thể thử với hàm tạo tĩnh để khởi tạo biến tĩnh. Cách tốt nhất là cung cấp đối tượng locking riêng biệt, vì vậy bạn có quyền kiểm soát tốt hơn mức độ chi tiết của khóa.

3

Something như thế này nên làm như lừa:

public static class ExplorationManager 
{ 
    public static int Counter = 0; 
    private static object _lock = new object(); 

    public static void ExplorerMaker(List<int[]> validPaths, List<string> myParents, string[,] myExplorationMap, List<int[]> myPositions) 
    { 
     foreach (var thread in validPaths.Select 
     (path => new Explorer(myParents, path, myExplorationMap, myPositions)). 
     Select(explorer => new Thread(explorer.Explore))) 
      { 
       thread.Name = "Thread of " + Counter + " generation"; 
       lock(_lock) 
       { 
        Counter++; 
        thread.Start(); 
       } 
    } 
} 
17

Sử dụng các lớp Interlocked:

Interlocked.Increment(ref Counter); 
1

Interlocked.Increment là một tùy chọn thread-safe. Rất đơn giản để sử dụng nếu bạn chỉ cần truy cập.

var newCounter = Interlocked.Increment(ref Counter) 
thread.Name = "Thread of " + (newCounter-1) + " generation"; 
Các vấn đề liên quan