2010-01-04 18 views
22

Vì vậy, tôi đã đọc về mô hình bộ nhớ là một phần của tiêu chuẩn C++ 0x sắp tới. Tuy nhiên, tôi là một chút bối rối về một số hạn chế cho những gì trình biên dịch được phép làm, đặc biệt là về tải trọng đầu cơ và các cửa hàng.Mô hình bộ nhớ C++ 0x và tải/lưu trữ đầu cơ

Để bắt đầu, một số công cụ có liên quan:

Hans Boehm's pages about threads and the memory model in C++0x

Boehm, "Threads Cannot be Implemented as a Library"

Boehm and Adve, "Foundations of the C++ Concurrency Memory Model"

Sutter, "Prism: A Principle-Based Sequential Memory Model for Microsoft Native Code Platforms", N2197

Boehm, "Concurrency memory model compiler consequences", N2338

Bây giờ, ý tưởng cơ bản về bản chất là "Sự nhất quán theo tuần tự cho các chương trình dữ liệu miễn phí", mà dường như là sự thỏa hiệp giữa lập trình dễ dàng và cho phép các cơ hội biên dịch và phần cứng tối ưu hóa. Một cuộc đua dữ liệu được xác định xảy ra nếu hai truy cập đến cùng một vị trí bộ nhớ bởi các luồng khác nhau không được sắp xếp, ít nhất một trong số chúng lưu trữ đến vị trí bộ nhớ, và ít nhất một trong số chúng không phải là một hành động đồng bộ hóa. Nó ngụ ý rằng tất cả truy cập đọc/ghi vào dữ liệu chia sẻ phải thông qua một số cơ chế đồng bộ hóa, chẳng hạn như mutexes hoặc hoạt động trên các biến nguyên tử (tốt, có thể hoạt động trên các biến nguyên tử với bộ nhớ thoải mái đặt hàng cho các chuyên gia chỉ, nhưng mặc định cung cấp cho tính nhất quán tuần tự).

Trong điều kiện này, tôi bị nhầm lẫn về các hạn chế về tải hoặc lưu trữ giả mạo hoặc đầu cơ trên các biến chia sẻ thông thường. Ví dụ, trong N2338 chúng ta có ví dụ

switch (y) { 
    case 0: x = 17; w = 1; break; 
    case 1: x = 17; w = 3; break; 
    case 2: w = 9; break; 
    case 3: x = 17; w = 1; break; 
    case 4: x = 17; w = 3; break; 
    case 5: x = 17; w = 9; break; 
    default: x = 17; w = 42; break; 
} 

mà trình biên dịch không được phép chuyển đổi thành

tmp = x; x = 17; 
switch (y) { 
    case 0: w = 1; break; 
    case 1: w = 3; break; 
    case 2: x = tmp; w = 9; break; 
    case 3: w = 1; break; 
    case 4: w = 3; break; 
    case 5: w = 9; break; 
    default: w = 42; break; 
} 

vì nếu y == 2 có một ghi giả mạo để x mà có thể là một vấn đề nếu một luồng khác đang cập nhật đồng thời x. Nhưng, tại sao đây lại là một vấn đề? Đây là một cuộc đua dữ liệu, dù sao cũng bị cấm; trong trường hợp này, trình biên dịch chỉ làm cho nó tồi tệ hơn bằng cách viết thành x hai lần, nhưng ngay cả một lần viết đơn sẽ là đủ cho một cuộc đua dữ liệu, phải không? I E. một chương trình C++ 0x thích hợp sẽ cần phải đồng bộ hóa quyền truy cập vào x, trong trường hợp đó sẽ không còn là cuộc đua dữ liệu nữa và cửa hàng giả mạo cũng không phải là vấn đề?

Tôi cũng bị nhầm lẫn tương tự về Ví dụ 3.1.3 trong N2197 và một số ví dụ khác, nhưng có thể giải thích cho vấn đề trên sẽ giải thích điều đó.

EDIT: Trả lời:

Lý do tại sao các cửa hàng đầu cơ là một vấn đề là trong ví dụ câu lệnh switch ở trên, các lập trình viên có thể được bầu vào điều kiện được khóa bảo vệ x chỉ khi y = 2! Do đó các cửa hàng đầu cơ có thể giới thiệu một cuộc đua dữ liệu mà không có trong mã ban đầu, và việc chuyển đổi như vậy là bị cấm. Cùng một đối số cũng áp dụng cho Ví dụ 3.1.3 trong N2197.

+0

Có thể một cho http://groups.google.com/group/comp.std.c++ –

Trả lời

7

Tôi không quen thuộc với tất cả những thứ bạn đề cập đến, nhưng lưu ý rằng trong y == 2 trường hợp, trong bit đầu tiên của mã, x không được viết ở tất cả (hoặc đọc, cho rằng vấn đề) . Trong bit thứ hai của mã, nó được viết hai lần. Đây là một sự khác biệt nhiều hơn là chỉ viết một lần so với viết hai lần (ít nhất, nó là trong các mô hình luồng hiện có như pthreads).Ngoài ra, lưu trữ một giá trị mà nếu không sẽ được lưu trữ ở tất cả là nhiều hơn một sự khác biệt so với chỉ lưu trữ một lần so với lưu trữ hai lần. Vì cả hai lý do này, bạn không muốn trình biên dịch chỉ thay thế một no-op bằng tmp = x; x = 17; x = tmp;.

Giả sử chủ đề A muốn giả định rằng không có chủ đề nào khác sửa đổi x. Đó là hợp lý để muốn nó được cho phép để mong đợi rằng nếu y là 2, và nó viết một giá trị cho x, sau đó đọc nó trở lại, nó sẽ lấy lại giá trị nó đã viết. Nhưng nếu thread B đồng thời thực thi bit thứ hai của bạn, thì luồng A có thể ghi vào x và sau đó đọc nó, và lấy lại giá trị ban đầu, bởi vì luồng B đã lưu "trước" ghi và phục hồi "sau" nó. Hoặc nó có thể lấy lại 17, bởi vì thread B được lưu trữ 17 "sau" ghi, và lưu trữ tmp lại "sau khi" thread A đọc. Chủ đề A có thể thực hiện bất kỳ việc đồng bộ hóa nào mà nó thích và nó sẽ không hữu ích, vì luồng B không được đồng bộ hóa. Lý do nó không được đồng bộ (trong y == 2 case) là nó không sử dụng x. Vì vậy, khái niệm về một bit cụ thể của mã "sử dụng x" là quan trọng đối với mô hình luồng, có nghĩa là trình biên dịch không thể được phép thay đổi mã để sử dụng x khi nó "không nên".

Tóm lại, nếu phép chuyển đổi bạn đề xuất được cho phép, giới thiệu viết giả, thì sẽ không bao giờ có thể phân tích một chút mã và kết luận rằng nó không sửa đổi x (hoặc bất kỳ vị trí bộ nhớ nào khác). Có một số thành ngữ thuận tiện do đó sẽ là không thể, chẳng hạn như chia sẻ dữ liệu bất biến giữa các chủ đề mà không đồng bộ hóa. Vì vậy, mặc dù tôi không quen thuộc với định nghĩa "cuộc đua dữ liệu" của C++ 0x, tôi cho rằng nó bao gồm một số điều kiện mà người lập trình được phép giả định rằng một đối tượng không được ghi vào đó. vi phạm những điều kiện đó. Tôi suy đoán rằng nếu y == 2, sau đó mã ban đầu của bạn, cùng với mã đồng thời: x = 42; x = 1; z = x trong một chủ đề khác, không được định nghĩa là một cuộc đua dữ liệu. Hoặc ít nhất nếu nó là một cuộc đua dữ liệu, nó không phải là một trong đó cho phép z để kết thúc với giá trị hoặc 17, hoặc 42.

Hãy xem xét rằng trong chương trình này, giá trị 2 trong y có thể được sử dụng để chỉ ra, "có là các chủ đề khác đang chạy: không sửa đổi x, bởi vì chúng tôi không được đồng bộ hóa ở đây, do đó sẽ giới thiệu một cuộc đua dữ liệu ". Có lẽ lý do không có đồng bộ hóa ở tất cả, là trong tất cả các trường hợp khác của y, không có chủ đề nào khác đang chạy với quyền truy cập vào x. Có vẻ hợp lý với tôi rằng C++ 0x muốn hỗ trợ mã như thế này:

if (single_threaded) { 
    x = 17; 
} else { 
    sendMessageThatSafelySetsXTo(17); 
} 

Rõ ràng sau đó, bạn không muốn điều đó chuyển thành:

tmp = x; 
x = 17; 
if (!single_threaded) { 
    x = tmp; 
    sendMessageThatSafelySetsXTo(17); 
} 

cơ bản Đó là sự chuyển đổi tương tự như trong ví dụ của bạn, nhưng chỉ với 2 trường hợp, thay vì có đủ để làm cho nó trông giống như một tối ưu hóa kích thước mã tốt.

+0

Trong C++ 0x, một cuộc đua dữ liệu xảy ra nếu hai truy cập vào cùng một vị trí bộ nhớ bởi các luồng khác nhau không được đặt hàng, tại ít nhất một trong số chúng lưu trữ vào vị trí bộ nhớ, và ít nhất một trong số chúng không phải là một hành động đồng bộ hóa. Tôi đã thêm câu hỏi này vào câu hỏi. – janneb

+0

+1 câu trả lời xuất sắc – swegi

+0

@janneb.Cảm ơn, trong trường hợp đó không có cuộc đua dữ liệu trong đoạn mã đầu tiên nếu y == 2 và chuỗi khác truy cập x, nhưng có một cuộc đua dữ liệu trong đoạn thứ hai nếu y == 2 và một chuỗi truy cập khác x. Rõ ràng trình biên dịch không được phép thêm các chủng tộc dữ liệu vào mã chủng tộc khác, hoặc toàn bộ mô hình là vô dụng. Do đó việc chuyển đổi bị cấm. –

4

Nếu y==2 và một chủ đề khác sửa đổi hoặc đọc x, thì có điều kiện chủng tộc trong mẫu ban đầu như thế nào? Chủ đề này không bao giờ chạm vào x, vì vậy các chủ đề khác có thể tự do làm như vậy. Tuy nhiên, với phiên bản sắp xếp lại, chủ đề của chúng tôi sửa đổi x, nếu chỉ tạm thời, vì vậy nếu một chủ đề khác cũng thao tác nó, bây giờ chúng tôi có một điều kiện chủng tộc mà không ai có mặt trước đây.

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