C #: Sự khác biệt giữa an toàn luồng và nguyên tử là gì?


Câu trả lời 1:

Chủ đề an toàn có nghĩa là không bị rối khi truy cập từ nhiều luồng; nguyên tử có nghĩa là không thể chia cắt, trong bối cảnh đó tương đương với không thể phá vỡ.

Để thực hiện khóa, bạn có hai lựa chọn:

  1. Có hỗ trợ phần cứng cho các hoạt động nguyên tử - các hướng dẫn tổng hợp đặc biệt thực thi toàn bộ, như Test-and-set.Be thông minh (và chịu hậu quả) - Thuật toán của Peterson.

Trong ví dụ của bạn trong các chi tiết, cả hai đều không an toàn; Nếu tôi hiểu chính xác, bạn có nghĩa là một cái gì đó như thế này:

lớp học không an toàn
{
    đối tượng riêng ulock = new object ();

    công khai Unsafe1 {get; bộ; } = 0;

    private int _unafe2 = 0;
    công cộng không an toàn2
    {
        được
        {
            khóa (ulock)
            {
                trả lại _unafe2;
            }
        }

        bộ
        {
            khóa (ulock)
            {
                _unafe2 = giá trị;
            }
        }
    }
}

Mã kiểm tra:

var u = new Không an toàn ();

Song song.For (0, 10000000, _ => {u.Unsafe1 ++;});
Song song.For (0, 10000000, _ => {u.Unsafe2 ++;});

Console.WriteLine (chuỗi.Format ("{0} - {1}", u.Unsafe1, u.Unsafe2));

Kết quả (một trong nhiều khả năng):

4648265 - 4149827

Đối với cả hai, hơn một nửa các bản cập nhật biến mất.

Lý do là ++ không phải là nguyên tử - nó thực sự có ba hoạt động riêng biệt:

  1. Nhận giá trị. Thêm 1 vào giá trị. Đặt giá trị.

Chúng ta có thể khắc phục điều này bằng cách cung cấp một hoạt động gia tăng mà nguyên tử của lòng đất - có nhiều cách để làm điều đó, nhưng đây là hai cách:

lớp học an toàn
{
    đối tượng riêng slock = new object ();

    công int Safe1 {get; bộ; }
    khoảng trống công khai SafeIncrement1 ()
    {
        khóa (ulock)
        {
            này.Safe1 ++;
        }
    }

    int int _safe2 = 0;
    công cộng an toàn2
    {
        được
        {
            trả về _safe2;
        }
        bộ
        {
            _safe2 = giá trị;
        }
    }
    khoảng trống công khai SafeIncrement2 ()
    {
        Liên khóa.Increment (ref _safe2);
    }
}

Mã kiểm tra:

var s = new An toàn ();

Song song.For (0, 10000000, _ => {s.SafeIncrement1 ();});
Song song.For (0, 10000000, _ => {s.SafeIncrement2 ();});

Console.WriteLine (chuỗi.Format ("{0} - {1}", s.Safe1, s.Safe2));

Kết quả là chính xác trong cả hai trường hợp. Cái đầu tiên chỉ đặt một khóa xung quanh toàn bộ hoạt động composite ++, trong khi cái thứ hai sử dụng hỗ trợ phần cứng cho các hoạt động nguyên tử.

Lưu ý biến thể thứ hai ở trên, với Interlocked.Increment, nhanh hơn nhiều, nhưng thực sự ở mức độ thấp hơn và bị giới hạn trong những gì nó có thể làm được ngoài hộp; tuy nhiên, các hoạt động trong gói Interlocked có thể được sử dụng để thực hiện:

  1. Các khóa quen thuộc - được gọi là đồng thời bi quan của Hồi giáo vì họ cho rằng hoạt động sẽ bị gián đoạn, vì vậy, don làm phiền bắt đầu cho đến khi họ có được một số tài nguyên được chia sẻ. So sánh và hoán đổi, bạn sử dụng một giá trị đặc biệt của hoàng tử canary mà bạn ghi lại khi bắt đầu, sau đó hãy chắc chắn rằng đã thay đổi theo bạn ở cuối; ý tưởng là nếu một chủ đề khác xuất hiện, nó sẽ giết chết chim hoàng yến, vì vậy bạn biết thử lại giao dịch của mình từ đầu. Điều này cũng đòi hỏi mã của bạn phải là nguyên tử - bạn không thể viết kết quả trung gian vào trạng thái chia sẻ, bạn phải thành công hoàn toàn hoặc thất bại hoàn toàn (như thể bạn đã thực hiện bất kỳ thao tác nào).

Câu trả lời 2:

Hai điều hoàn toàn khác nhau. Chủ đề an toàn có nghĩa là một hàm được viết theo cách mà nó có thể được gọi lặp đi lặp lại bởi nhiều luồng khác nhau, mà không làm cho mỗi luồng làm rối hoạt động của luồng khác (ví dụ: bằng cách thay đổi giá trị nếu một biến mà luồng khác đang sử dụng)

Nguyên tử có nghĩa là (nếu tôi đến nơi bạn đang đi với điều này) việc tạo ra một thể hiện của một đối tượng - vì vậy, bất kể nó có được nhắc đến thường xuyên như thế nào, bạn luôn thấy một thể hiện đó (từ bất kỳ luồng nào)


Câu trả lời 3:

Hoạt động nguyên tử là một cách để đạt được sự an toàn của luồng bằng cách sử dụng một số loại khóa như Mutexes hoặc Semaphores sử dụng hoạt động nguyên tử bên trong hoặc bằng cách thực hiện đồng bộ hóa khóa miễn phí bằng cách sử dụng hàng rào nguyên tử và bộ nhớ.

Vì vậy, các hoạt động nguyên tử trên các kiểu dữ liệu nguyên thủy là một công cụ để đạt được sự an toàn của luồng nhưng không đảm bảo an toàn luồng tự động vì thông thường bạn có nhiều hoạt động dựa vào nhau. Bạn phải đảm bảo rằng các thao tác này được thực hiện mà không bị gián đoạn, ví dụ như sử dụng Mutexes.

Đúng, viết một trong những kiểu dữ liệu nguyên tử này trong c # là luồng an toàn, nhưng điều đó không làm cho chức năng bạn sử dụng chúng trong luồng an toàn. Nó chỉ đảm bảo rằng một lần ghi được thực thi chính xác ngay cả khi một luồng thứ hai truy cập nó "cùng một lúc". Không bao giờ ít hơn, lần đọc tiếp theo từ luồng hiện tại không được đảm bảo để có được giá trị được viết trước đó như một luồng khác có thể đã được ghi vào nó, chỉ có điều giá trị đọc là hợp lệ.