2011-12-16 24 views
5

Tôi đang cố gắng thiết kế cấu trúc dữ liệu xung quanh một ngăn xếp chặn cho đến khi ngăn xếp có sẵn một mục. Tôi đã cố gắng sử dụng một AutoResetEvent nhưng tôi nghĩ rằng tôi hiểu lầm như thế nào mà quá trình đồng bộ hóa hoạt động. Về cơ bản, nhìn vào mã sau đây, tôi đang cố gắng để Pop từ ngăn xếp khi không có gì có sẵn.AutoResetEvent và nhiều Bộ

Dường như AutoResetEvent hoạt động như một semaphore. Đúng không? Tôi chỉ có thể loại bỏ các Set() trong BlockingStack.Get() và được thực hiện với nó? Hoặc điều đó sẽ dẫn đến một tình huống mà tôi chỉ sử dụng một trong các mục ngăn xếp của tôi.

public class BlockingStack 
{ 
    private Stack<MyType> _internalStack; 
    private AutoResetEvent _blockUntilAvailable; 

    public BlockingStack() 
    { 
     _internalStack = new Stack<MyType>(5); 
     _blockUntilAvailable = new AutoResetEvent(false); 

     for (int i = 0; i < 5; ++i) 
     { 
      var obj = new MyType(); 
      Add(obj); 
     } 
    } 

    public MyType Get() 
    { 
     _blockUntilAvailable.WatiOne(); 

     lock (_internalStack) 
     { 
      var obj = _internalStack.Pop(); 
      if (_internalStack.Count > 0) 
      { 
       _blockUntilAvailable.Set(); // do I need to do this? 
      } 

      return obj; 
     } 
    } 

    public void Add(MyType obj) 
    { 
     lock (_internalStack) 
     { 
      _internalStack.Push(obj); 
      _blockUntilAvailable.Set(); 
     } 
    } 
} 

Giả định của tôi là AutoResetEvent resets cho tất cả các chủ đề chờ đợi khi một người được qua các cuộc gọi WaitOne() chức năng. Tuy nhiên, có vẻ như nhiều chủ đề đang nhận được in Trừ khi tôi đã sai lầm logic của tôi ở đâu đó.

CHỈNH SỬA: Đây là dành cho Silverlight.

+0

Có liên quan: http://stackoverflow.com/questions/3797892/does-monitor-wait-needs-synchronization/3798033#3798033 –

Trả lời

1

tôi không xác minh các giải pháp Monitor dựa, nhưng tôi đã viết một giải pháp semaphore dựa trên dường như làm việc:

public class Semaphore 
{ 
    private int _count; 
    private int _maximum; 
    private object _countGuard; 

    public Semaphore(int maximum) 
    { 
     _count = 0; 
     _maximum = maximum; 
     _countGuard = new object(); 
    } 

    public void WaitOne() 
    { 
     while (true) 
     { 
      lock (_countGuard) 
      { 
       if (_count < _maximum) 
       { 
        _count++; 
        return; 
       } 
      } 
      Thread.Sleep(50); 
     } 
    } 

    public void ReleaseOne() 
    { 
     lock (_countGuard) 
     { 
      if (_count > 0) 
      { 
       _count--; 
      } 
     } 
    } 
} 

public class BlockingStack 
{ 
    private Stack<MyType> _internalStack; 
    private Semaphore _blockUntilAvailable; 

    public BlockingStack() 
    { 
     _internalStack = new Stack<MyType>(5); 
     _blockUntilAvailable = new Semaphore(5); 

     for (int i = 0; i < 5; ++i) 
     { 
      var obj = new MyType(); 
      Add(obj); 
     } 
    } 

    public MyType Get() 
    { 
     _blockUntilAvailable.WaitOne(); 

     lock (_internalStack) 
     { 
      var obj = _internalStack.Pop(); 
      return obj; 
     } 
    } 

    public void Add(MyType obj) 
    { 
     lock (_internalStack) 
     { 
      _internalStack.Push(obj); 
      _blockUntilAvailable.ReleaseOne(); 
     } 
    } 
} 
6

Bạn nên sử dụng bộ sưu tập chặn trừ khi bạn chỉ đang cố gắng hiểu cách hoạt động của luồng. Điều này sẽ cung cấp cho bạn một bộ sưu tập chặn được hỗ trợ bởi một ngăn xếp:

ConcurrentStack<SomeType> MyStack = new ConcurrentStack<SomeType>(); 
BlockingCollection<SomeType> SharedStack = new BlockingCollection<SomeType>(MyStack) 

Sau đó, bạn có thể truy cập bộ sưu tập theo kiểu chủ đề với tất cả chặn được thực hiện đúng cách. Xem here

Bạn có thể sử dụng sharedStack bằng cách gọi sharedStack.Take(), sau đó sẽ chặn cho đến khi có thứ gì đó lấy từ ngăn xếp.


Edit: Đã cho tôi một thời gian (và hai cố gắng) nhưng tôi đã làm việc ra vấn đề của bạn tôi nghĩ.

Xem xét ngăn xếp trống với 3 chuỗi đang chờ sự kiện.

Thêm được gọi, ngăn xếp có một đối tượng và một chuỗi được phép thông qua sự kiện.

Ngay lập tức Add được gọi lại.

Chuỗi đầu tiên thông qua bây giờ chờ để lấy khóa từ Thêm.

Tiện ích bổ sung thêm đối tượng thứ hai vào ngăn xếp và cho phép một chuỗi khác thông qua sự kiện.

Bây giờ, hai đối tượng trên ngăn xếp và 2 luồng thông qua sự kiện, cả hai đều đang chờ khóa.

Đầu tiên Tải chủ đề ngay bây giờ lấy khóa và bật. Một đối tượng trên stack vẫn còn và CALLS SET.

Chủ đề thứ ba được phép mặc dù sự kiện.

Thứ hai Nhận chuỗi ngay bây giờ lấy khóa và bật. Không có gì trong ngăn xếp và không gọi thiết lập.

NHƯNG. Đã quá muộn. Chuỗi thứ ba đã được cho phép thông qua, vì vậy khi chuỗi thứ hai từ bỏ khóa, chuỗi thứ ba cố gắng bật từ một ngăn xếp trống và ném.

+0

Điều này không trả lời câu hỏi OP - * Tôi đang cố gắng thiết kế cấu trúc dữ liệu xung quanh một st ack mà khối cho đến khi ngăn xếp có một mục có sẵn. * – oleksii

+0

Không có nó không đặc biệt - đó là lý do tại sao tôi lưu ý anh/cô ấy chỉ có thể muốn hiểu những gì đang xảy ra. Nếu họ đang thực sự cố gắng tạo cấu trúc này để sử dụng mặc dù tùy chọn tốt hơn là sử dụng bộ sưu tập chặn (nếu sử dụng .net4). Việc tạo các bộ sưu tập chặn là khó để có được đúng và rất dễ dàng để có được sai theo những cách tinh tế. –

+1

Đó chính là điều mà bộ sưu tập chặn thực hiện. Bạn gọi Take() trên nó và nó chặn cho đến khi có một cái gì đó có sẵn để lấy từ bộ sưu tập cơ bản. –

1

Không, mã hiện tại của bạn không có ý nghĩa.Tại thời điểm bạn đang chặn luồng mọi lúc, phương thức Get được gọi (cuộc gọi .WaitOne).

Bạn có thể muốn một cái gì đó như:

public class BlockingStack<T> 
{ 
    private Stack<T> _internalStack; 
    private AutoResetEvent _blockUntilAvailable; 

    public BlockingStack() 
    { 
     _internalStack = new Stack<T>(5); 
     _blockUntilAvailable = new AutoResetEvent(false); 
    } 

    public T Pop() 
    { 
     lock (_internalStack) 
     { 
      if (_internalStack.Count == 0) 
       _blockUntilAvailable.WaitOne(); 

      return _internalStack.Pop(); 
     } 
    } 

    public void Push(T obj) 
    { 
     lock (_internalStack) 
     { 
      _internalStack.Push(obj); 

      if(_internalStack.Count == 0) 
       _blockUntilAvailable.Set(); 
     } 
    } 
} 

Ý tưởng là, nếu số lượng hiện tại của các mục trong _internalStack là 0, sau đó nó nên chờ đợi một tín hiệu từ phương pháp Push. Một khi nó được báo hiệu, nó di chuyển và bật một mục từ ngăn xếp.


EDIT: Có 2 vấn đề với đoạn code trên:

  1. Bất cứ khi nào Pop khối với .WaitOne, nó không nhả khóa trên _internalStack, và do đó Push có thể không bao giờ lấy khóa.

  2. Khi Pop được gọi nhiều lần trên cùng một chuỗi, chúng chia sẻ cùng một initialState cho AutoResetEvent - ví dụ: Đẩy tín hiệu AutoResetEvent khi một mục được thêm vào. Bây giờ khi tôi bật một mục, nó hoạt động tốt lần đầu tiên, vì thực tế có một mục trong số Stack. Tuy nhiên, lần thứ hai, không có giá trị trong số Stack do đó nó chờ bằng cách gọi .WaitOne trên số AutoResetEvent - nhưng kể từ cuộc gọi đến Push báo hiệu sự kiện này, nó sẽ chỉ trả lại đúng và không chờ đợi như mong đợi.

An (làm việc) thay thế:

public class BlockingStack<T> 
{ 
    private Stack<T> _internalStack; 

    public BlockingStack() 
    { 
     _internalStack = new Stack<T>(5); 
    } 

    public T Pop() 
    { 
     lock (_internalStack) 
     { 
      if (_internalStack.Count == 0) 
       Monitor.Wait(_internalStack); 

      return _internalStack.Pop(); 
     } 
    } 

    public void Push(T obj) 
    { 
     lock (_internalStack) 
     { 
      _internalStack.Push(obj); 
      Monitor.Pulse(_internalStack); 
     } 
    } 
} 
+1

Không hoạt động, gọi Push, Pop, Pop, Push và quan sát bế tắc. –

+0

@HansPassant, Tại sao bạn gọi Push, Pop, Pop, Push trên một chủ đề? Bạn có mong đợi nó gọi Push bằng cách nào đó trên cùng một luồng, mặc dù Pop đang chặn luồng? – ebb

+0

Đẩy trên một sợi, Bật trên một sợi khác. –

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