2009-04-15 54 views
12

tôi vẫn đang bối rối ... Khi chúng tôi viết một số điều như thế này:Khóa() {} khóa tài nguyên, hay nó khóa một đoạn mã?

Object o = new Object(); 
var resource = new Dictionary<int , SomeclassReference>(); 

... và có hai khối mã khóa o khi truy cập resource ...

//Code one 
lock(o) 
{ 
    // read from resource  
} 

//Code two 
lock(o) 
{ 
    // write to resource 
} 

Bây giờ, nếu tôi có hai chủ đề, với một chuỗi thực thi luồng mà đọc từ resource và một văn bản khác, tôi muốn khóa resource sao cho khi nó được đọc, người viết sẽ phải đợi (và ngược lại - nếu nó đang được viết, độc giả sẽ phải đợi). Cấu trúc khóa có giúp tôi không? ... hay tôi nên sử dụng cái gì khác?

(Tôi đang sử dụng Dictionary cho các mục đích của ví dụ này, nhưng có thể là bất cứ điều gì)

Có hai trường hợp tôi đặc biệt quan tâm đến:

  1. hai luồng cố gắng để thực hiện cùng một dòng mã
  2. hai luồng cố gắng làm việc trên cùng một tài nguyên

Will lock giúp đỡ trong cả hai điều kiện?

+0

@SilverHorse: xin lỗi vì đã dẫm lên bản chỉnh sửa của bạn - hãy quay lại để thêm thẻ và không nhận ra bạn đã chỉnh sửa trong khi đó! – Shog9

Trả lời

12

Hầu hết các câu trả lời khác giải quyết ví dụ về mã của bạn, vì vậy tôi sẽ cố gắng trả lời câu hỏi của bạn trong tiêu đề.

Khóa thực sự chỉ là một mã thông báo. Ai có mã thông báo có thể lên sân khấu để nói chuyện. Do đó đối tượng bạn đang khóa không có kết nối rõ ràng với tài nguyên bạn đang cố gắng đồng bộ hóa xung quanh. Miễn là tất cả độc giả/nhà văn đồng ý trên cùng một mã thông báo thì nó có thể là bất cứ điều gì.

Khi cố gắng khóa trên một đối tượng (tức là bằng cách gọi Monitor.Enter trên một đối tượng), thời gian chạy kiểm tra xem khóa đã được giữ bởi một sợi chưa. Nếu đây là trường hợp chủ đề cố gắng khóa bị treo, nếu không thì nó sẽ lấy khóa và tiến hành thực thi.

Khi một chủ đề giữ khóa thoát khỏi phạm vi khóa (ví dụ: cuộc gọi Monitor.Exit), khóa được giải phóng và bất kỳ chủ đề chờ nào hiện có thể có khóa.

Cuối cùng một vài điều cần lưu ổ khóa tâm về:

  • Khóa chừng nào bạn cần, nhưng không còn.
  • Nếu bạn sử dụng Monitor.Enter/Exit thay vì từ khóa lock, hãy đảm bảo đặt cuộc gọi đến Exit trong khối finally để khóa được giải phóng ngay cả trong trường hợp ngoại lệ.
  • Việc phơi sáng đối tượng để khóa sẽ khiến bạn khó có thể có được cái nhìn tổng quan về ai đang khóa và khi nào. Các hoạt động đồng bộ lý tưởng nên được đóng gói.
+1

không hiểu điều này "Hiển thị đối tượng để khóa trên làm cho nó khó khăn hơn để có được một cái nhìn tổng quan về những người đang khóa và khi nào. Các hoạt động được đồng bộ hóa lý tưởng phải được đóng gói" Cảm ơn –

+0

Nếu đối tượng được sử dụng để khóa có thể truy cập bên ngoài loại, nó trở nên khó xác định ai đang khóa. Mặt khác, nếu tất cả khóa được đóng gói, bạn có thể dễ dàng xem tổng quan về khóa. –

+0

không rõ ràng. u có nghĩa là đối tượng 'o' mà tôi đã sử dụng shud được đúng trong một loại phải không? –

0

Và điều đó có tác dụng giả định rằng bạn chỉ có một quy trình liên quan. Bạn sẽ muốn sử dụng một "Mutex" nếu bạn muốn làm việc trên nhiều hơn thì một quá trình. Oh, và đối tượng "o", nên là một singleton hoặc scoped trên khắp mọi nơi khóa là cần thiết, vì những gì là thực sự bị khóa là đối tượng và nếu bạn tạo một cái mới, thì cái mới sẽ không được bị khóa.

2

Cả hai khối mã đều bị khóa ở đây. Nếu chủ đề khóa một khối đầu tiên, và chuỗi hai cố gắng để vào khối thứ hai, nó sẽ phải đợi.

+0

Có hai trường hợp ... 1) hai thredas cố gắng để thực hiện cùng một dòng mã 2) hai luồng cố gắng làm việc trên cùng một tài nguyên sẽ khóa giúp đỡ trong cả hai điều kiện –

5

Có, sử dụng khóa là đúng cách để đi. Bạn có thể khóa trên bất kỳ đối tượng nào, nhưng như đã đề cập trong các câu trả lời khác, việc khóa tài nguyên của bạn có lẽ là dễ nhất và an toàn nhất.

Tuy nhiên, bạn có thể muốn sử dụng cặp khóa đọc/ghi thay vì chỉ một khóa duy nhất, để giảm chi phí đồng thời.

Lý do cho điều đó là nếu bạn chỉ có một chủ đề viết, nhưng một số chủ đề đọc, bạn không muốn một hoạt động đọc để chặn một hoạt động đọc khác, nhưng chỉ đọc khối đọc hoặc ngược lại.

Bây giờ, tôi là một anh chàng java, vì vậy bạn sẽ phải thay đổi cú pháp và đào một số tài liệu để áp dụng trong C#, nhưng rw-locks là một phần của gói đồng thời chuẩn trong Java, vì vậy bạn có thể viết một cái gì đó như :

public class ThreadSafeResource<T> implements Resource<T> { 
    private final Lock rlock; 
    private final Lock wlock; 
    private final Resource res; 

    public ThreadSafeResource(Resource<T> res) { 
     this.res = res; 
     ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
     this.rlock = rwl.readLock(); 
     this.wlock = rwl.writeLock(); 
    } 

    public T read() { 
     rlock.lock(); 
     try { return res.read(); } 
     finally { rlock.unlock(); } 
    } 

    public T write(T t) { 
     wlock.lock(); 
     try { return res.write(t); } 
     finally { wlock.unlock(); } 
    } 
} 

Nếu ai đó có thể đưa ra một mẫu mã C# ...

+0

bất kỳ ý tưởng làm thế nào để áp dụng mà đọc viết cặp khóa? –

+3

Việc khóa tài nguyên không phải là ý tưởng hay nếu tài nguyên đó hiển thị bên ngoài mã nội bộ của anh ta. Điều đó có thể dẫn đến deadlocks hoặc điều kiện chủng tộc. bạn nên khóa một đối tượng riêng để ngăn chặn kịch bản đó. –

+0

Vâng, trong trường hợp đó bạn * muốn * muốn ngăn chặn các mã đồng thời nhận thức khác để gây rối với tài nguyên (không an toàn) của bạn trong khi bạn đang làm công cụ trên nó, vì vậy bạn * làm * muốn khóa trên đó. – Varkhan

0

cách bạn có nó thực hiện là một cách chấp nhận được để làm những gì bạn cần phải làm. Một cách để cải thiện cách làm của bạn là sử dụng khóa() trên chính từ điển, chứ không phải là đối tượng thứ hai được sử dụng để đồng bộ hóa từ điển. Bằng cách đó, thay vì đi qua một vật thể phụ, bản thân tài nguyên sẽ theo dõi xem có khóa trên màn hình riêng của nó hay không.

Sử dụng một đối tượng riêng biệt có thể hữu ích trong một số trường hợp, chẳng hạn như đồng bộ hóa quyền truy cập vào tài nguyên bên ngoài, nhưng trong các trường hợp như thế này là phí.

2

Các khóa (o) {...} tuyên bố được biên dịch như sau:

Monitor.Enter(o) 
try { ... } 
finally { Monitor.Exit(o) } 

Các cuộc gọi đến Monitor.Enter() sẽ chặn thread nếu thread khác đã gọi nó. Nó sẽ chỉ được bỏ chặn sau khi thread khác được gọi là Monitor.Exit() trên đối tượng.

2

Sẽ khóa trợ giúp trong cả hai điều kiện? Có.

Khóa() {} có khóa tài nguyên hay không nó khóa một đoạn mã?

lock(o) 
{ 
    // read from resource  
} 

là cú pháp đường cho

Monitor.Enter(o); 
try 
{ 
    // read from resource 
} 
finally 
{ 
    Monitor.Exit(o); 
} 

Các Monitor lớp giữ bộ sưu tập của các đối tượng mà bạn đang sử dụng để đồng bộ hóa quyền truy cập vào các khối mã. Đối với mỗi đối tượng đồng bộ hóa, Monitor giữ:

  1. Một tham chiếu đến các chủ đề mà hiện đang nắm giữ khóa trên các đối tượng đồng bộ hóa; tức là đến lượt của luồng này để thực thi.
  2. Hàng đợi "sẵn sàng" - danh sách các chuỗi đang chặn cho đến khi chúng được cung cấp khóa cho đối tượng đồng bộ hóa này.
  3. Hàng đợi "chờ" - danh sách các chuỗi chặn cho đến khi chúng được chuyển đến hàng đợi "sẵn sàng" Monitor.Pulse() hoặc Monitor.PulseAll().

Vì vậy, khi một chủ đề gọi khóa (o), nó được đặt trong hàng đợi sẵn sàng của o, cho đến khi nó được khóa trên o, tại thời điểm nó tiếp tục thực thi mã của nó.

+0

Nên là 'cuối cùng' và không 'bắt' chắc chắn. Xin lỗi để nitpick :) – Andy

+0

@Andy - Có. Đã cập nhật câu trả lời. – mbeckish

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