Tôi đọc một số bài viết về volatile
từ 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?
Tôi đọc một số bài viết về volatile
từ 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?
Câu trả lời:
Đố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ó".
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 i
thành 6, phiên bản tối ưu hóa vẫn sẽ in 5.
Các volatile
từ 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.
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 .
i
là 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" final
khi nó không được khai báo rõ ràng như vậy.
Để 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.
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.
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.
Đọ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 foo
là 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ự foo
thay đổi giá trị trước khi bộ nhớ ghi trong hàm Thing
tạ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 foo
là volatile
sau đó các cửa hàng để foo
sẽ 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 foo
có thể nhìn thấy bộ vi xử lý khác trước khi cho phép ghi vào foo
xả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 foo
nằ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 MySingleton
tạ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.
volatile
khô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ứ.
head
và tail
cần phải biến động để ngăn nhà sản xuất giả định tail
sẽ không thay đổi và để ngăn người tiêu dùng giả định head
sẽ không thay đổi. Ngoài ra, head
phả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 head
hiển thị trên toàn cầu.
Các từ khóa dễ bay hơi có ý nghĩa khác nhau trong cả hai Java và C #.
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.
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.
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).
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.
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.