Là một nguyên tử đọc / ghi bool trong C #


84

Đang truy cập nguyên tử trường bool trong C #? Đặc biệt, tôi có cần đặt một khóa xung quanh:

class Foo
{
   private bool _bar;

   //... in some function on any thread (or many threads)
   _bar = true;

   //... same for a read
   if (_bar) { ... }
}


1
Có, nhưng (có thể) cũng có. Có, truy cập / thiết lập trường bool là nguyên tử, NHƯNG thao tác if không phải là (tham khảo câu trả lời của Dror Helper bên dưới), vì vậy bạn vẫn có thể cần khóa.
JPProgrammer

Câu trả lời:


119

Đúng.

Đọc và ghi các kiểu dữ liệu sau là kiểu nguyên tử: bool, char, byte, sbyte, short, ushort, uint, int, float và các kiểu tham chiếu.

như được tìm thấy trong Thông số ngôn ngữ C # .

Chỉnh sửa: Có lẽ cũng nên hiểu từ khóa dễ bay hơi .


10
Con trỏ bản thân, giao lại nó, là nguyên tử (tức là Foo foo1 = foo2;
user142350

4
@configurator: Câu hỏi là chính xác bạn muốn gì. Rất dễ nhận sai các chương trình không khóa; vì vậy, trừ khi bạn thực sự cần nó, tốt hơn là sử dụng một khuôn khổ đơn giản hơn (ví dụ như TPL). Nói cách khác, 'dễ bay hơi' không sai, nhưng là dấu hiệu của mã phức tạp (tức là nên tránh). OP vẫn chưa thực sự nói ra những gì anh ấy muốn, tôi chỉ do dự khi đề xuất các loại willy-nilly dễ bay hơi.
Eamon Nerbonne

4
Khỉ thật. Đây là một từ ngữ nguy hiểm, đối với người C ++, nguyên tử có nghĩa là bất kỳ lần đọc ghi nào cũng được bao quanh bởi hàng rào bộ nhớ tương ứng. Điều này chắc chắn không phải là trường hợp trong C #. Bởi vì nếu không thì hiệu suất sẽ rất khủng khiếp vì nó bắt buộc đối với tất cả các biến <long. Nguyên tử ở đây theo cách nói của C #, dường như có ý nghĩa hơn là khi việc đọc hoặc ghi cuối cùng xảy ra, chúng được đảm bảo không bao giờ ở trạng thái bị hỏng . Nhưng nó không cho biết khi nào là "cuối cùng".
v.oddou

4
Nếu viết thành int và long là nguyên tử, thì khi sử dụng Interlocked.Add(ref myInt);ví dụ?
Mike de Klerk

6
@MikedeKlerk Việc đọc và ghi là nguyên tử, nhưng riêng biệt. i++bằng i=i+1, nghĩa là bạn đọc nguyên tử, sau đó cộng, sau đó viết nguyên tử. Một luồng khác có thể sửa đổi isau khi đọc nhưng trước khi ghi. Ví dụ: hai luồng thực hiện i++đồng thời trên cùng một luồng, tôi có thể đọc cùng một lúc (và do đó đọc cùng một giá trị), hãy thêm một luồng vào nó và sau đó cả hai ghi cùng một giá trị, hiệu quả chỉ thêm một lần. Interlocked.Add ngăn chặn điều này. Theo nguyên tắc chung, thực tế là một kiểu là nguyên tử chỉ hữu ích nếu chỉ có một luồng ghi nhưng nhiều luồng đọc.
Jannis Froese,

49

Như đã nói ở trên bool là nguyên tử nhưng bạn vẫn cần nhớ rằng nó cũng phụ thuộc vào những gì bạn muốn làm với nó.

if(b == false)
{
    //do something
}

không phải là một hoạt động nguyên tử có nghĩa là giá trị b có thể thay đổi trước khi luồng hiện tại thực thi mã sau câu lệnh if.


29

truy cập bool thực sự là nguyên tử, nhưng đó không phải là toàn bộ câu chuyện.

Bạn không phải lo lắng về việc đọc một giá trị 'được viết không đầy đủ' - không rõ điều đó có thể có ý nghĩa gì đối với bool trong mọi trường hợp - nhưng bạn phải lo lắng về bộ nhớ đệm của bộ xử lý, ít nhất là nếu chi tiết về thời gian là một vấn đề. Nếu luồng số 1 chạy trên lõi A có _bartrong bộ nhớ cache của bạn và _barđược cập nhật bởi luồng số 2 đang chạy trên lõi khác, thì luồng số 1 sẽ không thấy thay đổi ngay lập tức trừ khi bạn thêm khóa, khai báo _bardưới dạng volatilehoặc chèn rõ ràng các lệnh gọi Thread.MemoryBarrier()để làm mất hiệu lực giá trị được lưu trong bộ nhớ cache.


1
"không rõ điều đó có thể có nghĩa là gì đối với bool trong mọi trường hợp" Các mục chỉ tồn tại trong một byte bộ nhớ ở nguyên tử vì toàn bộ byte được ghi vào cùng một lúc. Các mục khác như double tồn tại trong nhiều byte, một byte có thể được ghi trước byte kia và bạn có thể quan sát vị trí bộ nhớ được ghi một nửa.
MindStalker

3
MemoryBarrier () không làm mất hiệu lực của bất kỳ bộ nhớ cache nào của bộ xử lý. Trong một số kiến ​​trúc, bộ xử lý được phép sắp xếp lại các lần đọc và ghi vào bộ nhớ chính để thực hiện. Việc sắp xếp lại có thể xảy ra miễn là từ quan điểm của một luồng đơn lẻ, ngữ nghĩa vẫn giữ nguyên. MemoryBarrier () yêu cầu bộ xử lý giới hạn việc sắp xếp lại để các hoạt động bộ nhớ phát ra trước hàng rào không được sắp xếp lại theo cách mà chúng kết thúc sau hàng rào.
TiMoch

1
Rào cản bộ nhớ rất hữu ích nếu bạn tạo một đối tượng béo và chuyển một tham chiếu đến nó có thể được đọc từ các luồng khác. Rào cản đảm bảo tham chiếu không được cập nhật trong bộ nhớ chính trước phần còn lại của đối tượng béo. Các luồng khác được đảm bảo sẽ không bao giờ thấy cập nhật tham chiếu trước khi đối tượng fat thực sự có sẵn trong bộ nhớ chính. var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
TiMoch

1

cách tiếp cận mà tôi đã sử dụng và tôi nghĩ là đúng, là

volatile bool b = false;

.. rarely signal an update with a large state change...

lock b_lock
{
  b = true;
  //other;
}

... another thread ...

if(b)
{
    lock b_lock
    {
       if(b)
       {
           //other stuff
           b = false;
       }
     }
}

mục đích về cơ bản là để tránh phải khóa lặp đi lặp lại một đối tượng trên mỗi lần lặp chỉ để kiểm tra xem chúng ta có cần khóa nó hay không để cung cấp một lượng lớn thông tin thay đổi trạng thái hiếm khi xảy ra. Tôi nghĩ rằng cách tiếp cận này hiệu quả. Và nếu cần nhất quán tuyệt đối, tôi nghĩ rằng tính dễ bay hơi sẽ phù hợp với b bool.


4
Đây thực sự là một cách tiếp cận đúng để khóa nói chung, nhưng nếu bools là nguyên tử, thì việc bỏ qua khóa sẽ đơn giản hơn (và nhanh hơn).
dbkk

2
Nếu không có khóa thì "sự thay đổi trạng thái lớn" sẽ không được thực hiện về mặt nguyên tử. Khóa -> thiết lập | kiểm tra -> khóa -> cách tiếp cận kiểm tra cũng sẽ đảm bảo rằng mã "// khác" được thực thi TRƯỚC mã "// nội dung khác". Giả sử phần "một chủ đề khác" đang lặp lại nhiều lần (đó là trong trường hợp của tôi), chỉ phải kiểm tra bool, hầu hết thời gian, nhưng không thực sự có được khóa (có thể cạnh tranh), là một chiến thắng hiệu suất chính
stux

1
Vâng, nếu bạn có lock(), bạn không cần volatile.
xmedeko
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.