Từ khóa trên mạng dễ bay hơi được sử dụng để làm gì?


130

Tôi đọc một số bài viết về volatiletừ khóa nhưng tôi không thể tìm ra cách sử dụng chính xác của nó. Bạn có thể vui lòng cho tôi biết nó nên được sử dụng cho C # và trong Java không?


1
Một trong những vấn đề với sự biến động là nó có nghĩa là nhiều hơn một điều. Nó là thông tin cho trình biên dịch không thực hiện tối ưu hóa sôi nổi là một di sản C. Nó cũng có nghĩa là các rào cản bộ nhớ nên được sử dụng khi truy cập. Nhưng trong hầu hết các trường hợp, nó chỉ tốn chi phí hiệu suất và / hoặc gây nhầm lẫn cho mọi người. : P
AnorZaken

Câu trả lời:


93

Đối với cả C # và Java, "volility" cho trình biên dịch biết rằng giá trị của biến không bao giờ được lưu trong bộ nhớ cache vì giá trị của nó có thể thay đổi ngoài phạm vi của chính chương trình. Trình biên dịch sau đó sẽ tránh mọi tối ưu hóa có thể dẫn đến các vấn đề nếu biến thay đổi "nằm ngoài sự kiểm soát của nó".


@Tom - lưu ý hợp lệ, thưa ngài - và sửa đổi.
Sẽ

11
Nó vẫn còn nhiều hơn tinh tế hơn thế.
Tom Hawtin - tackline

1
Sai lầm. Không ngăn chặn bộ nhớ đệm. Xem câu trả lời của tôi.
doug65536

168

Xem xét ví dụ này:

int i = 5;
System.out.println(i);

Trình biên dịch có thể tối ưu hóa điều này để chỉ in 5, như thế này:

System.out.println(5);

Tuy nhiên, nếu có một chủ đề khác có thể thay đổi i, đây là hành vi sai. Nếu một luồng khác thay đổi ithành 6, phiên bản tối ưu hóa vẫn sẽ in 5.

Các volatiletừ khóa ngăn ngừa tối ưu hóa bộ nhớ đệm và như vậy, và do đó rất hữu ích khi một biến thể được thay đổi bởi thread khác.


3
Tôi tin rằng tối ưu hóa sẽ vẫn còn hiệu lực với iđánh dấu là volatile. Trong Java, tất cả là về các mối quan hệ xảy ra trước khi xảy ra .
Tom Hawtin - tackline

Cảm ơn bạn đã đăng bài, vì vậy bằng cách nào đó dễ bay hơi có kết nối với khóa biến?
Mircea

@Mircea: Đó là những gì tôi đã nói rằng việc đánh dấu một thứ gì đó không ổn định là tất cả về: đánh dấu một trường là dễ bay hơi sẽ sử dụng một số cơ chế bên trong để cho phép các luồng nhìn thấy một giá trị nhất quán cho biến đã cho, nhưng điều này không được đề cập trong câu trả lời ở trên ... Có lẽ ai đó có thể xác nhận điều này hay không? Cảm ơn
npinti

5
@Sjoerd: Tôi không chắc là tôi hiểu ví dụ này. Nếu ilà một biến cục bộ, không có luồng nào khác có thể thay đổi nó. Nếu đó là một trường, trình biên dịch không thể tối ưu hóa cuộc gọi trừ khi đó là final. Tôi không nghĩ trình biên dịch có thể thực hiện tối ưu hóa dựa trên giả định rằng một trường "trông" finalkhi nó không được khai báo rõ ràng như vậy.
đa gen

1
C # và java không phải là C ++. Điều này LAF không đúng. Nó không ngăn chặn bộ nhớ đệm và nó không ngăn chặn tối ưu hóa. Đó là về ngữ nghĩa đọc và lưu trữ phát hành, vốn được yêu cầu trên các kiến ​​trúc bộ nhớ được sắp xếp yếu. Đó là về thực hiện đầu cơ.
doug65536

40

Để hiểu những gì biến động làm cho một biến, điều quan trọng là phải hiểu những gì xảy ra khi biến không biến động.

  • Biến là không dễ bay hơi

Khi hai luồng A & B đang truy cập vào một biến không biến động, mỗi luồng sẽ duy trì một bản sao cục bộ của biến trong bộ đệm cục bộ của nó. Mọi thay đổi được thực hiện bởi luồng A trong bộ đệm cục bộ của nó sẽ không hiển thị đối với luồng B.

  • Biến là dễ bay hơi

Khi các biến được khai báo là không ổn định, về cơ bản có nghĩa là các luồng không nên lưu trữ một biến như vậy hoặc nói cách khác, các luồng không nên tin vào các giá trị của các biến này trừ khi chúng được đọc trực tiếp từ bộ nhớ chính.

Vì vậy, khi nào để làm cho một biến động dễ bay hơi?

Khi bạn có một biến có thể được truy cập bởi nhiều luồng và bạn muốn mọi luồng nhận được giá trị cập nhật mới nhất của biến đó ngay cả khi giá trị được cập nhật bởi bất kỳ luồng / tiến trình / bên ngoài chương trình nào khác.


2
Sai lầm. Nó không có gì để làm với "ngăn chặn bộ nhớ đệm". Đó là về sắp xếp lại, bởi trình biên dịch, HOẶC phần cứng CPU thông qua thực thi đầu cơ.
doug65536

37

Đọc các lĩnh vực dễ bay hơi đã có được ngữ nghĩa . Điều này có nghĩa là nó được đảm bảo rằng bộ nhớ đọc từ biến dễ bay hơi sẽ xảy ra trước khi bất kỳ bộ nhớ nào sau đây đọc. Nó chặn trình biên dịch thực hiện sắp xếp lại và nếu phần cứng yêu cầu nó (CPU được đặt hàng yếu), nó sẽ sử dụng một lệnh đặc biệt để làm cho phần cứng xóa bất kỳ lần đọc nào xảy ra sau khi đọc dễ bay hơi nhưng được khởi động sớm, hoặc CPU có thể bắt đầu sớm ngăn chặn chúng được ban hành sớm ở nơi đầu tiên, bằng cách ngăn chặn bất kỳ tải đầu cơ nào xảy ra giữa vấn đề tải trọng và nghỉ hưu.

Các bài viết của các lĩnh vực dễ bay hơi đã phát hành ngữ nghĩa . Điều này có nghĩa là nó được đảm bảo rằng mọi bộ nhớ ghi vào biến dễ bay hơi đều được đảm bảo bị trì hoãn cho đến khi tất cả các bộ nhớ ghi trước đó hiển thị cho các bộ xử lý khác.

Hãy xem xét ví dụ sau:

something.foo = new Thing();

Nếu foolà một biến thành viên trong một lớp và các CPU khác có quyền truy cập vào thể hiện đối tượng được đề cập bởi something, chúng có thể thấy sự foothay đổi giá trị trước khi bộ nhớ ghi trong hàm Thingtạo có thể nhìn thấy trên toàn cầu! Đây là ý nghĩa của "bộ nhớ yếu". Điều này có thể xảy ra ngay cả khi trình biên dịch có tất cả các cửa hàng trong hàm tạo trước khi lưu trữ tới foo. Nếu foovolatilesau đó các cửa hàng để foosẽ có ngữ nghĩa phát hành, và đảm bảo phần cứng mà tất cả các lần ghi trước khi ghi vào foocó thể nhìn thấy bộ vi xử lý khác trước khi cho phép ghi vào fooxảy ra.

Làm thế nào có thể cho các văn bản foođược sắp xếp lại rất tệ? Nếu việc giữ dòng bộ đệm foonằm trong bộ đệm và các cửa hàng trong hàm tạo đã bỏ qua bộ đệm, thì cửa hàng có thể hoàn thành sớm hơn nhiều so với ghi vào bộ nhớ cache.

Kiến trúc Itanium (khủng khiếp) từ Intel có bộ nhớ yếu. Bộ xử lý được sử dụng trong XBox 360 ban đầu có bộ nhớ được sắp xếp yếu. Nhiều bộ xử lý ARM, bao gồm ARMv7-A rất phổ biến có bộ nhớ được sắp xếp yếu.

Các nhà phát triển thường không nhìn thấy các cuộc đua dữ liệu này bởi vì những thứ như ổ khóa sẽ tạo ra một rào cản bộ nhớ đầy đủ, về cơ bản giống như việc mua và giải phóng ngữ nghĩa cùng một lúc. Không có tải bên trong khóa có thể được thực hiện theo suy đoán trước khi khóa được lấy, chúng bị trì hoãn cho đến khi khóa được lấy. Không có cửa hàng nào có thể bị trì hoãn trong quá trình phát hành khóa, hướng dẫn giải phóng khóa bị trì hoãn cho đến khi tất cả các ghi được thực hiện bên trong khóa được hiển thị trên toàn cầu.

Một ví dụ đầy đủ hơn là mẫu "Khóa được kiểm tra hai lần". Mục đích của mẫu này là để tránh phải luôn luôn có được khóa để lười khởi tạo một đối tượng.

Theo dõi từ Wikipedia:

public class MySingleton {
    private static object myLock = new object();
    private static volatile MySingleton mySingleton = null;

    private MySingleton() {
    }

    public static MySingleton GetInstance() {
        if (mySingleton == null) { // 1st check
            lock (myLock) {
                if (mySingleton == null) { // 2nd (double) check
                    mySingleton = new MySingleton();
                    // Write-release semantics are implicitly handled by marking
                    // mySingleton with 'volatile', which inserts the necessary memory
                    // barriers between the constructor call and the write to mySingleton.
                    // The barriers created by the lock are not sufficient because
                    // the object is made visible before the lock is released.
                }
            }
        }
        // The barriers created by the lock are not sufficient because not all threads
        // will acquire the lock. A fence for read-acquire semantics is needed between
        // the test of mySingleton (above) and the use of its contents. This fence
        // is automatically inserted because mySingleton is marked as 'volatile'.
        return mySingleton;
    }
}

Trong ví dụ này, các cửa hàng trong hàm MySingletontạo có thể không hiển thị với các bộ xử lý khác trước khi lưu trữ tới mySingleton. Nếu điều đó xảy ra, các luồng khác nhìn vào mySingleton sẽ không thu được khóa và chúng sẽ không nhất thiết phải ghi vào trình tạo.

volatilekhông bao giờ ngăn chặn bộ nhớ đệm. Những gì nó làm là đảm bảo thứ tự mà các bộ xử lý khác "nhìn thấy" ghi. Một bản phát hành cửa hàng sẽ trì hoãn một cửa hàng cho đến khi tất cả các bản ghi đang chờ xử lý hoàn tất và một chu kỳ xe buýt đã được phát hành nói với các bộ xử lý khác loại bỏ / ghi lại dòng bộ đệm của họ nếu chúng có các dòng liên quan được lưu trữ. Tải có được sẽ xóa mọi dữ liệu được suy đoán, đảm bảo rằng chúng sẽ không còn là giá trị cũ từ quá khứ.


Lời giải thích hay. Cũng tốt ví dụ kiểm tra khóa kép. Tuy nhiên, tôi vẫn không chắc chắn khi nào nên sử dụng vì tôi lo lắng về các khía cạnh bộ đệm. Nếu tôi viết một triển khai hàng đợi trong đó chỉ có 1 luồng sẽ được viết và chỉ có 1 luồng sẽ được đọc, tôi có thể nhận được mà không có khóa và chỉ đánh dấu "con trỏ" đầu và đuôi của tôi là dễ bay hơi không? Tôi muốn đảm bảo rằng cả người đọc và người viết đều thấy các giá trị cập nhật nhất.
nickdu

Cả hai headtailcần phải biến động để ngăn nhà sản xuất giả định tailsẽ không thay đổi và để ngăn người tiêu dùng giả định headsẽ không thay đổi. Ngoài ra, headphải có tính biến động để đảm bảo rằng dữ liệu hàng đợi ghi được hiển thị trên toàn cầu trước khi cửa hàng headhiển thị trên toàn cầu.
doug65536

+1, Điều khoản như mới nhất / "cập nhật nhất" không may ngụ ý một khái niệm về giá trị đúng số ít. Trong thực tế, hai đối thủ cạnh tranh có thể vượt qua một vạch kết thúc cùng một lúc - trên lõi cpu, hai lõi có thể yêu cầu viết cùng một lúc . Rốt cuộc, lõi không thay phiên nhau làm việc - điều đó sẽ làm cho đa lõi trở nên vô nghĩa. Tư duy / thiết kế đa luồng tốt không nên tập trung vào việc cố gắng ép buộc "tính mới nhất" ở mức độ thấp - vốn dĩ là giả vì khóa chỉ buộc các lõi tự ý chọn một loa một cách công bằng - mà là cố gắng thiết kế đi cần cho một khái niệm không tự nhiên như vậy.
AnorZaken

34

Các từ khóa dễ bay hơi có ý nghĩa khác nhau trong cả hai Java và C #.

Java

Từ Spec ngôn ngữ Java :

Một trường có thể được khai báo là không ổn định, trong trường hợp đó, mô hình bộ nhớ Java đảm bảo rằng tất cả các luồng đều thấy một giá trị nhất quán cho biến.

C #

Từ tài liệu tham khảo C # về từ khóa dễ bay hơi :

Từ khóa dễ bay hơi chỉ ra rằng một trường có thể được sửa đổi trong chương trình bằng một cái gì đó như hệ điều hành, phần cứng hoặc một luồng thực thi đồng thời.


Cảm ơn bạn rất nhiều vì đã đăng, vì tôi hiểu trong Java, nó hoạt động như khóa biến đó trong ngữ cảnh luồng và trong C # nếu được sử dụng, giá trị của biến có thể được thay đổi không chỉ từ chương trình, các yếu tố bên ngoài như HĐH có thể sửa đổi giá trị của nó ( không khóa ngụ ý) ... Xin vui lòng cho tôi biết nếu tôi hiểu đúng những khác biệt đó ...
Mircea

@Mircea trong Java không có khóa liên quan, nó chỉ đảm bảo rằng giá trị cập nhật nhất của biến dễ bay hơi sẽ được sử dụng.
krock

Java có hứa hẹn một số loại rào cản bộ nhớ, hay nó giống như C ++ và C # chỉ hứa hẹn không tối ưu hóa tham chiếu đi?
Steven Sudit

Rào cản bộ nhớ là một chi tiết thực hiện. Điều Java thực sự hứa hẹn là tất cả các lần đọc sẽ thấy giá trị được viết bởi lần viết gần đây nhất.
Stephen C

1
@StevenSudit Có, nếu phần cứng yêu cầu rào cản hoặc tải / thu hoặc lưu trữ / phát hành thì nó sẽ sử dụng các hướng dẫn đó. Xem câu trả lời của tôi.
doug65536

9

Trong Java, "volility" được sử dụng để nói với JVM rằng biến có thể được sử dụng bởi nhiều luồng cùng một lúc, do đó, tối ưu hóa chung nhất định không thể được áp dụng.

Đáng chú ý là tình huống hai luồng truy cập vào cùng một biến đang chạy trên các CPU riêng biệt trong cùng một máy. Rất phổ biến đối với CPU để lưu trữ dữ liệu dữ liệu mà nó lưu giữ vì truy cập bộ nhớ chậm hơn rất nhiều so với truy cập bộ đệm. Điều này có nghĩa là nếu dữ liệu được cập nhật trong CPU1, nó phải ngay lập tức đi qua tất cả các bộ nhớ cache và vào bộ nhớ chính thay vì khi bộ đệm quyết định tự xóa, để CPU2 có thể thấy giá trị được cập nhật (một lần nữa bằng cách bỏ qua tất cả các bộ đệm trên đường đi).


1

Khi bạn đang đọc dữ liệu không biến động, luồng thực thi có thể hoặc không luôn luôn nhận được giá trị cập nhật. Nhưng nếu đối tượng không ổn định, luồng luôn nhận được giá trị cập nhật nhất.


1
Bạn có thể viết lại câu trả lời của bạn?
Anirudha Gupta

từ khóa dễ bay hơi sẽ cung cấp cho bạn giá trị cập nhật nhất thay vì giá trị được lưu trữ.
Subhash Saini

0

Biến động là giải quyết vấn đề tương tranh. Để làm cho giá trị đó đồng bộ. Từ khóa này chủ yếu được sử dụng trong một luồng. Khi nhiều luồng cập nhật cùng một biến.


1
Tôi không nghĩ rằng nó "giải quyết" vấn đề. Đó là một công cụ giúp đỡ trong một số trường hợp. Đừng phụ thuộc vào sự biến động trong các tình huống cần khóa, như trong điều kiện cuộc đua.
Scratte
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.