2011-08-09 26 views
24

Tôi đang làm việc trên một cái gì đó sử dụng ByteBuffers được tạo từ các tệp được ánh xạ trên bộ nhớ (qua FileChannel.map()) cũng như ByteBuffers trực tiếp trong bộ nhớ. Tôi đang cố gắng hiểu các ràng buộc mô hình đồng thời và bộ nhớ.Có thể xem nhiều chuỗi ghi trên một ByteBuffer được ánh xạ trực tiếp trong Java không?

Tôi đã đọc tất cả Javadoc (và nguồn) có liên quan cho những thứ như FileChannel, ByteBuffer, MappedByteBuffer, v.v. Có vẻ rõ ràng rằng một ByteBuffer cụ thể (và các lớp con có liên quan) có một loạt các trường và trạng thái không được bảo vệ từ quan điểm của mô hình bộ nhớ. Vì vậy, bạn phải đồng bộ hóa khi sửa đổi trạng thái của một ByteBuffer cụ thể nếu bộ đệm đó được sử dụng trên các luồng. thủ đoạn thường gặp bao gồm sử dụng một ThreadLocal để quấn ByteBuffer, lặp lại (trong khi đồng bộ) để có được một trường hợp mới trỏ đến các byte ánh xạ cùng vv

Với kịch bản này:

  1. quản lý có một bộ đệm byte ánh xạ B_all cho toàn bộ tệp (giả sử là < 2gb)
  2. các cuộc gọi của người quản lý trùng lặp(), position(), limit() và slice() trên B_all để tạo một ByteBuffer nhỏ hơn B_1. cho chủ đề T1
  3. người quản lý thực hiện tất cả các công cụ tương tự để tạo ByteBuffer B_2 trỏ đến các byte ánh xạ tương tự và đưa ra này cho chủ đề T2

Câu hỏi của tôi là: Có thể T1 ghi vào B_1 và T2 ghi vào B_2 đồng thời và được đảm bảo để xem những thay đổi của nhau? T3 có thể sử dụng B_all để đọc các byte đó và được đảm bảo để xem các thay đổi từ cả T1 và T2?

Tôi biết rằng việc ghi trong một tệp được ánh xạ không nhất thiết được thấy trên quy trình trừ khi bạn sử dụng force() để hướng dẫn hệ điều hành viết các trang xuống đĩa. Tôi không quan tâm về điều đó. Giả sử cho câu hỏi này là JVM này là quá trình duy nhất viết một tệp ánh xạ duy nhất.

Lưu ý: Tôi không tìm kiếm các dự đoán (tôi có thể tự mình làm tốt). Tôi muốn tham chiếu đến một cái gì đó dứt khoát về những gì là (hoặc không) được đảm bảo cho bộ nhớ đệm ánh xạ trực tiếp. Hoặc nếu bạn có kinh nghiệm thực tế hoặc các trường hợp thử nghiệm âm tính, điều đó cũng có thể phục vụ như là bằng chứng đầy đủ.

Cập nhật: Tôi đã thực hiện một số thử nghiệm có nhiều chuỗi ghi vào cùng một tệp song song và cho đến nay có vẻ như những ghi đó ngay lập tức hiển thị với các chuỗi khác. Tôi không chắc liệu tôi có thể dựa vào điều đó không.

+0

Tôi đọc api cho MappedByteBuffer (Java 7) và họ cảnh báo rằng nó chỉ nên được sử dụng để đọc/ghi, chứ không phải thao tác. – toto2

+0

Có, câu hỏi của tôi là về hai chủ đề viết. Tôi không hiểu ý bạn là "thao tác". –

+0

JavaDoc của MappedByteBuffer: "Tất cả hoặc một phần của bộ đệm byte được ánh xạ có thể trở thành không thể truy cập bất kỳ lúc nào, ví dụ: nếu tệp được ánh xạ bị cắt bớt. Do đó, các trường hợp ngoại lệ không được chỉ định sẽ được đưa ra tại thời điểm truy cập hoặc vào một thời điểm nào đó sau đó, nên thực hiện các biện pháp phòng ngừa thích hợp để tránh ** thao tác ** của tệp được ánh xạ bởi chương trình này hoặc đồng thời chạy chương trình, ngoại trừ việc đọc hoặc ghi nội dung của tệp. " – toto2

Trả lời

0

Tôi không nghĩ rằng điều này được đảm bảo. Nếu mô hình bộ nhớ Java không nói rằng nó được đảm bảo thì nó không được bảo đảm bởi định nghĩa. Tôi hoặc là bảo vệ bộ đệm ghi với đồng bộ hoặc hàng đợi viết cho một sợi xử lý tất cả các ghi. Sau này chơi độc đáo với bộ nhớ đệm đa lõi (tốt hơn để có 1 nhà văn cho mỗi vị trí RAM).

+0

Chúng ta đang nói về bộ nhớ được ánh xạ tới một vùng đĩa (off-heap). Tôi không thấy lý do nào để cho rằng nó có cùng ràng buộc. Bạn có thể cung cấp một tham chiếu đến hiệu ứng đó không? –

1

Tôi cho rằng bộ nhớ trực tiếp cung cấp cùng một bảo đảm hoặc thiếu chúng như bộ nhớ heap. Nếu bạn sửa đổi một ByteBuffer chia sẻ một mảng bên dưới hoặc địa chỉ bộ nhớ trực tiếp, ByteBuffer thứ hai là một luồng khác có thể thấy các thay đổi, nhưng không được bảo đảm để làm như vậy.

Tôi nghi ngờ ngay cả khi bạn sử dụng đồng bộ hoặc dễ bay hơi, nó vẫn không được bảo đảm để hoạt động, tuy nhiên nó cũng có thể làm như vậy tùy thuộc vào nền tảng.

Một cách đơn giản để thay đổi dữ liệu giữa các chủ đề là sử dụng một Exchanger

Dựa trên ví dụ này,

class FillAndEmpty { 
    final Exchanger<ByteBuffer> exchanger = new Exchanger<ByteBuffer>(); 
    ByteBuffer initialEmptyBuffer = ... a made-up type 
    ByteBuffer initialFullBuffer = ... 

    class FillingLoop implements Runnable { 
    public void run() { 
     ByteBuffer currentBuffer = initialEmptyBuffer; 
     try { 
     while (currentBuffer != null) { 
      addToBuffer(currentBuffer); 
      if (currentBuffer.remaining() == 0) 
      currentBuffer = exchanger.exchange(currentBuffer); 
     } 
     } catch (InterruptedException ex) { ... handle ... } 
    } 
    } 

    class EmptyingLoop implements Runnable { 
    public void run() { 
     ByteBuffer currentBuffer = initialFullBuffer; 
     try { 
     while (currentBuffer != null) { 
      takeFromBuffer(currentBuffer); 
      if (currentBuffer.remaining() == 0) 
      currentBuffer = exchanger.exchange(currentBuffer); 
     } 
     } catch (InterruptedException ex) { ... handle ...} 
    } 
    } 

    void start() { 
    new Thread(new FillingLoop()).start(); 
    new Thread(new EmptyingLoop()).start(); 
    } 
} 
+1

Tôi rất KHÔNG giả định rằng bộ nhớ trực tiếp có cùng bảo đảm như bộ nhớ heap. Nếu đó là sự thật, tôi muốn xem một tham chiếu đến một cái gì đó mà nói như vậy. –

0

Không, nó không có gì khác biệt so với các biến java bình thường hoặc phần tử mảng.

+0

Tại sao bạn lại nói vậy? –

+0

Tại sao nó không khác biệt? Dữ liệu cơ bản không phải là các biến Java hoặc các phần tử mảng, nó là bộ nhớ được ánh xạ được cung cấp bởi hệ điều hành. – EJP

1

Một câu trả lời có thể tôi đã thực hiện là sử dụng các khóa tệp để giành quyền truy cập độc quyền vào phần đĩa được ánh xạ bởi bộ đệm. Điều này được giải thích với ví dụ here chẳng hạn.

Tôi đoán rằng điều này thực sự sẽ bảo vệ phần đĩa để ngăn đồng thời ghi trên cùng một phần của tệp. Điều tương tự có thể đạt được (trong một JVM đơn lẻ nhưng không nhìn thấy được với các quá trình khác) với các màn hình dựa trên Java cho các phần của tệp đĩa. Tôi đoán rằng sẽ nhanh hơn với nhược điểm là không nhìn thấy được với các quy trình bên ngoài.

Tất nhiên, tôi muốn tránh việc khóa tệp hoặc đồng bộ hóa trang nếu tính nhất quán được đảm bảo bởi jvm/os.

+0

Thử hỏi điều này trong danh sách Đồng thời. Tôi đã hỏi một cái gì đó tương tự như liên quan đến byte trên heap [] - [http://markmail.org/thread/2sldp7bz4k5aq5ei] và [http://markmail.org/thread/wpcu55p6psarvyho]. Nhưng không có ý tưởng làm thế nào bộ nhớ ánh xạ xử lý đồng thời. –

15

Ánh xạ bộ nhớ với JVM chỉ là một trình bao bọc mỏng xung quanh CreateFileMapping (Windows) hoặc mmap (posix). Như vậy, bạn có quyền truy cập trực tiếp vào bộ đệm đệm của hệ điều hành. Điều này có nghĩa rằng các bộ đệm này là những gì hệ điều hành xem xét các tập tin chứa (và hệ điều hành cuối cùng sẽ đồng bộ các tập tin để phản ánh điều này).

Vì vậy, không cần gọi buộc() để đồng bộ hóa giữa các quá trình. Các quá trình đã được đồng bộ hóa (thông qua hệ điều hành - ngay cả đọc/ghi truy cập vào cùng một trang). Buộc chỉ đồng bộ giữa hệ điều hành và bộ điều khiển ổ đĩa (có thể có sự chậm trễ giữa bộ điều khiển ổ đĩa và đĩa cứng vật lý, nhưng bạn không có hỗ trợ phần cứng để làm bất cứ điều gì về điều đó).

Bất kể, các tệp ánh xạ bộ nhớ là một dạng bộ nhớ dùng chung được chấp nhận giữa các luồng và/hoặc quy trình. Sự khác biệt duy nhất giữa bộ nhớ chia sẻ này và, một khối bộ nhớ ảo được đặt tên trong Windows là đồng bộ hóa cuối cùng cho đĩa (trên thực tế, mmap thực hiện bộ nhớ ảo mà không có một điều tập tin bằng ánh xạ/dev/null). Việc đọc ghi bộ nhớ từ nhiều tiến trình/chủ đề vẫn cần một số đồng bộ, vì bộ vi xử lý có thể thực hiện out-of-order (không chắc chắn điều này tương tác với JVM như thế nào, nhưng bạn không thể tạo ra các giả định), nhưng viết một byte từ một luồng sẽ có cùng sự bảo đảm bằng cách ghi vào bất kỳ byte nào trong heap bình thường. Một khi bạn đã viết cho nó, mọi luồng, và mọi tiến trình, sẽ thấy bản cập nhật (thậm chí thông qua một hoạt động mở/đọc).

Mọi chi tiết, nhìn lên mmap trong posix (hoặc CreateFileMapping cho Windows, được xây dựng gần như cùng một cách.

+0

Câu trả lời xuất sắc; bóc tấm lại và đưa ra các chi tiết cụ thể cho tất cả ngôn ngữ "hành vi không xác định" trong JDs. Cảm ơn Paul. –

+1

Mặc dù công việc này và tương đối khó thay đổi nhưng điều này không được đảm bảo. JavaDoc cho các trạng thái ByteBuffer: _Given một bộ đệm byte trực tiếp, máy ảo Java sẽ thực hiện một ** nỗ lực tốt nhất ** để thực hiện các hoạt động I/O riêng trực tiếp trên nó. Tức là, nó sẽ ** cố gắng ** tránh sao chép nội dung của bộ đệm vào (hoặc từ) một bộ đệm trung gian trước (hoặc sau) mỗi lần gọi một trong các hoạt động I/O gốc của hệ điều hành cơ sở ._ "Cố gắng" và " nỗ lực tốt nhất "có nghĩa là việc triển khai không bắt buộc/được bảo đảm để hoạt động theo cách bạn muốn. –

3

Điều rẻ nhất bạn có thể làm là sử dụng một biến không ổn định. Sau một sợi ghi vào ánh xạ Bất kỳ chuỗi đọc nào cũng nên đọc biến dễ bay hơi trước khi đọc bộ đệm được ánh xạ, làm điều này tạo ra "xảy ra-trước" trong mô hình bộ nhớ Java. đảm bảo rằng một quá trình khác là ở giữa viết một cái gì đó mới.Nhưng nếu bạn muốn đảm bảo rằng các chủ đề khác có thể nhìn thấy một cái gì đó bạn đã viết, viết một biến động (tiếp theo là đọc nó từ thứ e đọc thread) sẽ làm các trick.

4

No. Mô hình bộ nhớ JVM (JMM) không đảm bảo rằng nhiều luồng biến đổi (không đồng bộ) dữ liệu sẽ thấy mỗi thay đổi khác.Đầu tiên, tất cả các chủ đề truy cập vào bộ nhớ chia sẻ đều nằm trong cùng một JVM, thực tế là bộ nhớ này đang được truy cập thông qua một ByteBuffer được ánh xạ là không liên quan (không có biến động tiềm ẩn hoặc đồng bộ trên bộ nhớ truy cập thông qua ByteBuffer). , do đó, câu hỏi tương đương với một câu hỏi về việc truy cập một mảng byte.

Hãy rephrase câu hỏi để về mảng byte của nó:

  1. Một nhà quản lý có một mảng byte: byte[] B_all
  2. Một tham chiếu mới cho mảng được tạo ra: byte[] B_1 = B_all, và trao cho chủ đề T1
  3. Một tham chiếu khác cho mảng đó được tạo: byte[] B_2 = B_all và được cung cấp cho chủ đề T2

Làm ghi lên B_1 theo chủ đề T1 được xem trong B_2 theo chủ đề T2?

Không, ghi như vậy không được bảo đảm để xem, mà không có sự đồng bộ hóa rõ ràng giữa T_1T_2. Cốt lõi của vấn đề là JIT của JVM, bộ vi xử lý và kiến ​​trúc bộ nhớ được tự do sắp xếp lại một số truy cập bộ nhớ (không chỉ để đẩy bạn ra ngoài mà còn cải thiện hiệu suất thông qua bộ nhớ đệm). Tất cả các lớp này mong đợi phần mềm rõ ràng (thông qua các khóa, dễ bay hơi hoặc các gợi ý rõ ràng khác) về việc yêu cầu đồng bộ hóa ở đâu, ngụ ý các lớp này được tự do di chuyển mọi thứ xung quanh khi không có gợi ý nào được cung cấp. Lưu ý rằng trong thực tế cho dù bạn thấy viết hay không phụ thuộc chủ yếu vào phần cứng và sự liên kết của dữ liệu ở các mức khác nhau của bộ đệm và đăng ký, và cách "xa" các chủ đề đang chạy trong hệ thống phân cấp bộ nhớ.

JSR-133 là một nỗ lực để xác định chính xác Mô hình bộ nhớ Java xoay quanh Java 5.0 (và theo như tôi biết nó vẫn được áp dụng trong năm 2012). Đó là nơi bạn muốn tìm câu trả lời dứt khoát (mặc dù dày đặc): http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf (phần 2 có liên quan nhất). Bạn có thể tìm thấy các nội dung dễ đọc hơn trên trang web JMM: http://www.cs.umd.edu/~pugh/java/memoryModel/

Câu trả lời của tôi là khẳng định rằng ByteBuffer không khác với byte[] về mặt đồng bộ hóa dữ liệu. Tôi không thể tìm thấy tài liệu cụ thể mà nói điều này, nhưng tôi đề nghị rằng "An toàn chủ đề" của tài liệu java.nio.Buffer sẽ đề cập đến một cái gì đó về đồng bộ hóa hoặc dễ bay hơi nếu điều đó được áp dụng. Vì doc không đề cập đến điều này, chúng ta không nên mong đợi hành vi như vậy.

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