2011-07-29 26 views
9

Dường như có rất nhiều điều để tìm hiểu về lập trình đa luồng và tất cả đều hơi đáng sợ.Cách đơn giản nhất để tạo toàn bộ một phương thức an toàn cho luồng?

Đối với nhu cầu hiện tại của tôi, tôi chỉ muốn bảo vệ chống lại một phương pháp được gọi là một lần nữa từ một thread trước khi nó kết thúc, và câu hỏi của tôi là:

Đây có phải là một đầy đủ (an toàn) cách để làm cho một phương pháp thread-an toàn?

class Foo 
{ 
    bool doingWork; 
    void DoWork() 
    { 
     if (doingWork) // <- sophistocated thread-safety 
      return;  // <- 

     doingWork = true; 

     try 
     { 
      [do work here] 
     } 
     finally 
     { 
      doingWork = false; 
     } 
    } 
} 

Nếu điều đó không đủ, cách đơn giản nhất để đạt được điều này là gì?


EDIT: Thông tin thêm về các kịch bản:

  • Chỉ có một thể hiện của Foo

  • Foo.DoWork() sẽ được gọi từ một sợi ThreadPool trên Đã trôi qua sự kiện của System.Timers.Timer.

  • Thông thường Foo.DoWork() sẽ hoàn thành eons trước khi nó được gọi là , nhưng tôi muốn mã cho cơ hội mỏng sẽ chạy dài, và được gọi lại trước khi hoàn tất.


(Tôi cũng không đủ thông minh để chắc chắn nếu câu hỏi này có thể được gắn thẻ ngôn ngữ-agnostic, vì vậy tôi không có. Độc giả giác ngộ, cảm thấy tự do để làm như vậy nếu có.)

+0

Đối tượng của loại Foo được khởi tạo cho mỗi chuỗi hay được chia sẻ qua nhiều luồng? – NotMe

+0

Cung cấp thêm chi tiết về mã gọi phương thức dowork, có nhiều luồng không? – sll

+0

Hoặc hỗ trợ ủy quyền lại hoặc thiết kế mã của bạn để nó không thể xảy ra.Chỉ cần bailing ra khi nó xảy ra là không có khả năng là giải pháp đúng. Nhưng chỉnh sửa của bạn làm cho nó rõ ràng rằng an toàn thread thực sự là vấn đề chứ không phải là entrancy. –

Trả lời

8

Mã của bạn không phải là chủ đề an toàn. Thay vào đó, bạn nên sử dụng từ khóa lock.

Trong mã hiện tại của bạn:

if (doingWork) 
     return; 

    // A thread having entered the function was suspended here by the scheduler. 

    doingWork = true; 

Khi thread kế tiếp đi qua, nó cũng sẽ nhập hàm.

Đây là lý do nên sử dụng cấu trúc lock. Về cơ bản, nó giống như mã của bạn, nhưng không có rủi ro cho một chuỗi bị gián đoạn ở giữa:

class Foo 
{ 
    object lockObject = new object; 
    void DoWork() 
    { 
     lock(lockObject) 
     { 
      [do work here] 
     } 
    } 
} 

Lưu ý rằng mã này có ngữ nghĩa hơi khác so với ban đầu của bạn. Mã này sẽ khiến luồng thứ hai nhập vào chờ và sau đó thực hiện công việc. Mã ban đầu của bạn đã tạo chuỗi thứ hai chỉ cần hủy bỏ. Để đến gần mã ban đầu của bạn, bạn không thể sử dụng câu lệnh C# lock. Các cơ bản Monitor xây dựng phải được sử dụng trực tiếp:

class Foo 
{ 
    object lockObject = new object; 
    void DoWork() 
    { 
     if(Monitor.TryEnter(lockObject)) 
     { 
      try 
      { 
       [do work here] 
      } 
      finally 
      { 
       Monitor.Exit(lockObject); 
      } 
     } 
    } 
} 
+0

Tôi không thực sự cần phải hủy bỏ, do đó, phương pháp khóa nên được tốt. Ngọt - thật đơn giản! Cảm ơn. –

+0

[Valentin] (http://stackoverflow.com/questions/6879468/simplest-way-to-make-a-whole-method-thread-safe/6879698#6879698) có cách để hủy bỏ chỉ với khóa (). Quan tâm đến nhận xét của bạn về nó. –

0

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

Có thể muốn xem xét sử dụng rào chắn thay vào đó, tất cả đều phù hợp với bạn. Đó là cách tiêu chuẩn để kiểm soát mã reentrant. Cũng cho phép bạn kiểm soát số lượng các chủ đề làm công việc đó cùng một lúc (nếu bạn cho phép nhiều hơn 1).

+0

Điều đó có vẻ quá phức tạp đối với tôi cần. Tôi thực sự chỉ muốn đạt được những gì đoạn mã của tôi ngụ ý: nhảy ra khi nhập lại. –

+0

Kiểm tra số người tham gia trong hàng rào, nếu đó là> 0 trả lại, hãy thêm người tham gia và thực hiện công việc, cuối cùng, hãy xóa người tham gia. Barrier là có nghĩa là để được an toàn thread. –

+0

bạn có thể kiểm tra và thêm người tham gia theo cách nguyên tử không? Nếu vậy, làm thế nào? –

3

Re-entrancy không liên quan gì đến việc đa luồng.

Phương pháp tiếp cận lại là một phương pháp có thể kết thúc được gọi từ bên trong chính nó, trên cùng một chuỗi.
Ví dụ: nếu phương thức tăng sự kiện và mã khách hàng xử lý sự kiện đó gọi lại phương thức bên trong trình xử lý sự kiện, thì phương thức đó sẽ được thực hiện lại.
Bảo vệ phương thức đó khỏi việc ủy ​​thác lại có nghĩa là đảm bảo rằng nếu bạn gọi nó từ bên trong chính nó, nó sẽ không làm bất kỳ thứ gì hoặc ném một ngoại lệ.

Mã của bạn được bảo vệ khỏi quyền truy cập lại trong cùng một cá thể đối tượng, miễn là mọi thứ đều nằm trên cùng một chuỗi.

Trừ khi [do work here] có khả năng chạy mã bên ngoài (ví dụ: bằng cách tăng sự kiện hoặc bằng cách gọi đại biểu hoặc phương thức từ một sự kiện khác), nó không phải là người tham gia lại.

Câu hỏi đã chỉnh sửa của bạn cho biết rằng toàn bộ phần này không liên quan đến bạn.
Có lẽ bạn vẫn nên đọc nó.


Bạn có thể (EDIT: là) tìm kiếm độc quyền – đảm bảo rằng phương pháp này sẽ không chạy hai lần cùng một lúc nếu gọi bằng nhiều luồng cùng một lúc.
Mã của bạn không độc quyền. Nếu hai luồng chạy phương thức cùng một lúc và cả hai đều chạy câu lệnh if cùng một lúc, cả hai đều sẽ vượt qua if, sau đó cả hai đặt cờ doingWork và cả hai sẽ chạy toàn bộ phương thức.

Để làm điều đó, hãy sử dụng từ khóa lock.

+0

Vâng nó đã nói đa luồng trong các thẻ vì vậy người ta chỉ có thể giả định. –

+1

Bạn chỉ có thể giả định _what_? Câu hỏi không liên quan gì đến việc đa luồng. ** EDIT **: Bây giờ, nó có. – SLaks

+0

Phải thừa nhận rằng tôi chỉ đang nghĩ về phương pháp được gọi lại từ một chủ đề khác. Cảm ơn bạn đã chỉ ra điều này - tôi đã chỉnh sửa câu hỏi để rõ ràng về điều đó. Tuy nhiên tôi tin rằng thuật ngữ có thể được áp dụng cho một kịch bản đa luồng quá. Nếu không, bạn sẽ gọi nó là gì khi một phương thức cần có khả năng đối phó với việc được gọi từ một luồng khác trong khi nó đã thực thi? –

2

nếu bạn muốn dễ mã và không quan tâm về hiệu suất quá nhiều nó có thể được dễ dàng như

class Foo 
{ 
    bool doingWork; 
object m_lock=new object(); 
    void DoWork() 
    { 
     lock(m_lock) // <- not sophistocated multithread protection 
{ 
     if (doingWork) 
      return;  
     doingWork = true; 
} 


     try 
     { 
      [do work here] 
     } 
     finally 
     { 
lock(m_lock) //<- not sophistocated multithread protection 
{ 
      doingWork = false; 
} 
     } 
    } 

}

Nếu bạn muốn incapsulate khóa một chút, bạn có thể tạo ra một tài sản đó là thread an toàn như thế này:

public bool DoingWork 
{ 
get{ lock(m_Lock){ return doingWork;}} 
set{lock(m_lock){doingWork=value;}} 
} 

Bây giờ bạn có thể sử dụng nó thay vì trường, tuy nhiên nó sẽ dẫn đến nhiều thời gian hơn cho khóa gây ra số lượng sử dụng khóa tăng lên.

Hoặc bạn có thể sử dụng đầy đủ hàng rào cách tiếp cận (từ cuốn sách luồng lớn Joseph Albahari online threading)

class Foo 
{ 
    int _answer; 
    bool _complete; 

    void A() 
    { 
    _answer = 123; 
    Thread.MemoryBarrier(); // Barrier 1 
    _complete = true; 
    Thread.MemoryBarrier(); // Barrier 2 
    } 

    void B() 
    { 
    Thread.MemoryBarrier(); // Barrier 3 
    if (_complete) 
    { 
     Thread.MemoryBarrier();  // Barrier 4 
     Console.WriteLine (_answer); 
    } 
    } 
} 

Ông khẳng định rằng toàn hàng rào là 2x nhanh hơn so với tuyên bố khóa. Trong một số trường hợp, bạn có thể cải thiện hiệu suất bằng cách loại bỏ các cuộc gọi không cần thiết tới MemoryBarrier(), nhưng sử dụng lock đơn giản, rõ ràng hơn và ít bị lỗi hơn.

Tôi tin rằng điều này cũng có thể được thực hiện bằng cách sử dụng lớp Interlocked xung quanh trường làm việc dựa trên int.

+0

Có bất kỳ sự khác biệt đáng chú ý nào giữa khóa của bạn() (khóa truy cập vào workingWork) và phương thức lock() của Anders (khóa truy cập vào mã đang thực hiện công việc) không? –

+0

có sự khác biệt về mặt logic. Như tôi có thể thấy từ phương pháp trả lời của ông là reentrant, và các cuộc gọi tiếp theo được xếp hàng đợi. Trong mã của tôi, tôi giả định rằng bạn không muốn cho phép các luồng khác xếp hàng phương thức này, nếu nó đang chạy trên một luồng. Theo kinh nghiệm của tôi, cách tiếp cận của tôi thường được ưu tiên, ví dụ khi bạn không muốn cho phép người dùng khởi chạy một số yêu cầu máy chủ bằng cách nhấp đúp vào một nút. PS: nơi ông nói rằng nó không thể được thực hiện bằng cách sử dụng khóa, tôi làm điều đó bằng cách sử dụng khóa. Monitor.TryEnter có thể có hiệu suất tốt hơn, nhưng một chút nâng cao hơn cho nhu cầu hàng ngày tôi đoán, id để nó cho mã hiệu suất quan trọng. –

+0

Ồ tôi hiểu rồi, tuyệt. Làm thế nào ya như thế, Anders? :) –

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