2011-09-18 42 views
26

Tôi đã thử tìm kiếm chi tiết về điều này, tôi thậm chí còn đọc tiêu chuẩn về mutex và atomics ... nhưng tôi vẫn không thể hiểu được khả năng hiển thị của mô hình bộ nhớ C++ 11. Từ những gì tôi hiểu các tính năng rất quan trọng của mutex BESIDE loại trừ lẫn nhau là đảm bảo khả năng hiển thị. Aka nó là không đủ mà chỉ có một chủ đề mỗi lần tăng bộ đếm, điều quan trọng là các chủ đề tăng truy cập đã được lưu trữ bởi các chủ đề được sử dụng cuối cùng mutex (tôi thực sự không biết tại sao mọi người không đề cập đến điều này nhiều hơn khi thảo luận mutexes, có lẽ tôi đã có giáo viên xấu :)). Vì vậy, từ những gì tôi có thể nói doesnt nguyên tử thực thi tầm nhìn trước mắt: (từ người đó duy trì đẩy mạnh :: chủ đề và đã thực hiện c chủ đề ++ 11 và thư viện mutex):Đặt hàng và hiển thị mô hình bộ nhớ?

Một hàng rào với memory_order_seq_cst không thi hành ngay lập tức khả năng hiển thị cho các chủ đề khác (và cũng không phải là hướng dẫn MFENCE). Các ràng buộc đặt hàng bộ nhớ C++ 0x chỉ là --- đặt hàng ràng buộc. memory_order_seq_cst hoạt động tạo thành một đơn đặt hàng tổng, nhưng không có giới hạn về thứ tự đó, ngoại trừ việc nó phải là được tất cả các chủ đề đồng ý và không được vi phạm các ràng buộc khác theo thứ tự . Cụ thể, các chuỗi có thể tiếp tục thấy các giá trị "cũ" trong một thời gian, miễn là chúng thấy giá trị theo thứ tự phù hợp với các ràng buộc.

Và tôi đồng ý với điều đó. Nhưng vấn đề là tôi gặp khó khăn khi hiểu những gì C++ 11 xây dựng liên quan đến nguyên tử là "toàn cầu" và chỉ đảm bảo tính thống nhất trên các biến nguyên tử. Đặc biệt tôi có sự hiểu biết đó (nếu có) của bộ nhớ sau orderings đảm bảo rằng sẽ có một hàng rào bộ nhớ trước và sau khi tải và lưu trữ: http://www.stdthread.co.uk/doc/headers/atomic/memory_order.html

Từ những gì tôi có thể nói std :: memory_order_seq_cst chèn hàng rào mem trong khi khác chỉ thực thi thứ tự của các hoạt động trên vị trí bộ nhớ nhất định.

Vì vậy, ai đó có thể xóa điều này, tôi đoán rất nhiều người sẽ làm lỗi khủng khiếp bằng cách sử dụng lệnh std :: atomic, esp nếu họ không sử dụng mặc định (std :: memory_order_seq_cst memory order)
2. if I ' m đúng không có nghĩa là dòng thứ hai là redundand trong mã này:

atomicVar.store(42); 
std::atomic_thread_fence(std::memory_order_seq_cst); 

3. làm std :: atomic_thread_fences có yêu cầu tương tự như mutexes theo một nghĩa nào đó để đảm bảo tính nhất quán seq trên nonatomic vars người ta phải làm std :: atomic_thread_fence (std :: memory_order_seq_cst); trước khi tải và tiêu chuẩn :: atomic_thread_fence (std :: memory_order_seq_cst);
sau cửa hàng?
4.

{ 
    regularSum+=atomicVar.load(); 
    regularVar1++; 
    regularVar2++; 
    } 
    //... 
    { 
    regularVar1++; 
    regularVar2++; 
    atomicVar.store(74656); 
    } 

tương đương với

std::mutex mtx; 
{ 
    std::unique_lock<std::mutex> ul(mtx); 
    sum+=nowRegularVar; 
    regularVar++; 
    regularVar2++; 
} 
//.. 
{ 
    std::unique_lock<std::mutex> ul(mtx); 
    regularVar1++; 
    regularVar2++; 
    nowRegularVar=(74656); 
} 

Tôi nghĩ là không, nhưng tôi muốn để đảm bảo.

EDIT: 5. Khẳng định cháy?
Chỉ tồn tại hai chủ đề.

atomic<int*> p=nullptr; 

thread đầu tiên viết

{ 
    nonatomic_p=(int*) malloc(16*1024*sizeof(int)); 
    for(int i=0;i<16*1024;++i) 
    nonatomic_p[i]=42; 
    p=nonatomic; 
} 

sợi thứ hai đọc

{ 
    while (p==nullptr) 
    { 
    } 
    assert(p[1234]==42);//1234-random idx in array 
} 

Trả lời

22

Nếu bạn thích để đối phó với hàng rào, sau đó a.load(memory_order_acquire) tương đương với a.load(memory_order_relaxed) Tiếp theo atomic_thread_fence(memory_order_acquire). Tương tự, a.store(x,memory_order_release) tương đương với một cuộc gọi đến atomic_thread_fence(memory_order_release) trước khi gọi đến a.store(x,memory_order_relaxed). memory_order_consume là trường hợp đặc biệt của memory_order_acquire, cho dữ liệu phụ thuộc chỉ. memory_order_seq_cst là đặc biệt và tạo thành tổng đơn đặt hàng trên tất cả các hoạt động memory_order_seq_cst. Trộn lẫn với những người khác, nó giống như việc có được một tải trọng, và một bản phát hành cho một cửa hàng. memory_order_acq_rel là dành cho các hoạt động đọc-sửa-ghi, và tương đương với một phần thu được trên phần đọc và một bản phát hành trên phần viết của RMW.

Việc sử dụng các ràng buộc đặt hàng đối với các hoạt động nguyên tử có thể hoặc không thể dẫn đến hướng dẫn hàng rào thực tế, tùy thuộc vào kiến ​​trúc phần cứng. Trong một số trường hợp trình biên dịch sẽ tạo ra mã tốt hơn nếu bạn đặt ràng buộc đặt hàng vào hoạt động nguyên tử thay vì sử dụng một hàng rào riêng biệt.

Trên x86, tải luôn được mua và các cửa hàng luôn được phát hành. memory_order_seq_cst yêu cầu đặt hàng mạnh hơn với hướng dẫn MFENCE hoặc hướng dẫn tiền tố LOCK (có một lựa chọn thực hiện ở đây là để làm cho cửa hàng có thứ tự mạnh hơn hoặc tải). Do đó, hàng rào độc lập và phát hành là không có ops, nhưng atomic_thread_fence(memory_order_seq_cst) không phải là (một lần nữa yêu cầu MFENCE hoặc LOCK hướng dẫn ed).

Một ảnh hưởng quan trọng của các ràng buộc đặt hàng là chúng đặt hàng các hoạt động khác.

std::atomic<bool> ready(false); 
int i=0; 

void thread_1() 
{ 
    i=42; 
    ready.store(true,memory_order_release); 
} 

void thread_2() 
{ 
    while(!ready.load(memory_order_acquire)) std::this_thread::yield(); 
    assert(i==42); 
} 

thread_2 quay cho đến khi nó đọc true từ ready.Kể từ khi cửa hàng để ready trong thread_1 là một thông cáo, và tải là một Acquire sau đó các cửa hàng đồng bộ hóa với các tải, và các cửa hàng để ixảy ra-trước tải từ i trong khẳng định, và khẳng định sẽ không cháy.

2) Dòng thứ hai trong

atomicVar.store(42); 
std::atomic_thread_fence(std::memory_order_seq_cst); 

thực sự là khả năng dư thừa, bởi vì các cửa hàng để sử dụng atomicVarmemory_order_seq_cst theo mặc định. Tuy nhiên, nếu có các hoạt động nguyên tử khác không phải là memory_order_seq_cst trên luồng này thì hàng rào có thể có hậu quả. Ví dụ, nó sẽ hoạt động như một hàng rào phát hành cho a.store(x,memory_order_relaxed) tiếp theo.

3) Hàng rào và hoạt động nguyên tử không hoạt động như mutexes. Bạn có thể sử dụng chúng để tạo các mutex, nhưng chúng không hoạt động giống chúng. Bạn không cần phải sử dụng atomic_thread_fence(memory_order_seq_cst). Không có yêu cầu bất kỳ hoạt động nguyên tử nào là memory_order_seq_cst và việc đặt hàng trên các biến phi nguyên tử có thể đạt được mà không cần, như trong ví dụ trên.

4) Không có giá trị này không tương đương. Đoạn mã của bạn không có khóa mutex là một cuộc đua dữ liệu và hành vi không xác định.

5) Không xác nhận của bạn không thể kích hoạt. Với thứ tự bộ nhớ mặc định của memory_order_seq_cst, cửa hàng và tải từ con trỏ nguyên tử p hoạt động giống như lưu trữ và tải trong ví dụ trên của tôi và các cửa hàng cho các phần tử mảng được đảm bảo xảy ra trước khi đọc.

+0

vì vậy trong 5) cho (int i = 0; i <16 * 1024; ++ i) nonatomic_p [i] = 42; không thể di chuyển sau khi gán p? Bởi vì memory_order_seq Tôi đoán tôi đúng, tôi chỉ muốn kiểm tra. BTW câu trả lời tuyệt vời! – NoSenseEtAl

+0

BTW bạn có thể mở rộng về lý do tại sao 4) là cuộc đua dữ liệu? – NoSenseEtAl

+1

Có, bạn đúng trong 5 --- không thể di chuyển các nhiệm vụ sang 'nonatomic_p [i]' sau khi gán 'p'. –

7

Từ những gì tôi có thể nói std :: memory_order_seq_cst chèn hàng rào mem trong khi khác chỉ thực thi Trật tự của các hoạt động trên vị trí bộ nhớ nhất định.

Nó thực sự phụ thuộc vào những gì bạn đang làm và nền tảng bạn đang làm việc. Mô hình đặt hàng bộ nhớ mạnh trên nền tảng như x86 sẽ tạo ra một bộ yêu cầu khác cho sự tồn tại của các hoạt động hàng rào bộ nhớ so với mô hình đặt hàng yếu hơn trên các nền tảng như IA64, PowerPC, ARM, vv. tùy thuộc vào nền tảng, hướng dẫn hàng rào bộ nhớ thích hợp sẽ được sử dụng. Trên một nền tảng như x86, không cần phải có hàng rào bộ nhớ đầy đủ trừ khi bạn đang thực hiện thao tác đọc-sửa-ghi. Mỗi mô hình bộ nhớ x86, tất cả các tải đều có ngữ nghĩa tải, và tất cả các cửa hàng đều có ngữ nghĩa giải phóng cửa hàng. Vì vậy, trong những trường hợp này, enum cơ bản tạo ra một no-op vì mô hình bộ nhớ cho x86 đã đảm bảo rằng các kiểu hoạt động đó nhất quán trên các luồng, và do đó không có hướng dẫn lắp ráp nào thực hiện các loại rào cản bộ nhớ một phần này. Do đó, điều kiện không-op tương tự sẽ đúng nếu bạn đặt rõ ràng cài đặt std::memory_order_release hoặc std::memory_order_acquire trên x86. Hơn nữa, đòi hỏi một rào cản bộ nhớ đầy đủ trong những tình huống này sẽ là một trở ngại hiệu suất không cần thiết. Như đã lưu ý, nó sẽ chỉ được yêu cầu cho các hoạt động đọc-sửa đổi cửa hàng. Tuy nhiên, trên các nền tảng khác với các mô hình nhất quán bộ nhớ yếu hơn, điều đó sẽ không xảy ra và do đó việc sử dụng std::memory_order_seq_cst sẽ sử dụng các hoạt động hàng rào bộ nhớ thích hợp mà không cần phải xác định rõ liệu họ có muốn tải, lưu trữ hay không phát hành, hoặc hoạt động hàng rào bộ nhớ đầy đủ. Các nền tảng này có các hướng dẫn cụ thể về máy để thực thi các hợp đồng nhất quán về bộ nhớ và cài đặt std::memory_order_seq_cst sẽ làm việc trong trường hợp phù hợp. Nếu người dùng muốn gọi cụ thể cho một trong các hoạt động này, họ có thể thông qua các loại enum rõ ràng std::memory_order, nhưng nó sẽ không cần thiết ... trình biên dịch sẽ làm việc ra các thiết lập chính xác.

Tôi đoán có rất nhiều người đang gonna được làm lỗi khủng khiếp using std :: nguyên tử, đặc biệt là nếu họ không sử dụng mặc định (std :: memory_order_seq_cst nhớ đặt hàng)

Vâng, nếu họ don' t biết họ đang làm gì và không hiểu loại ngữ nghĩa rào cản bộ nhớ nào được gọi trong một số hoạt động nhất định, thì sẽ có rất nhiều lỗi được thực hiện nếu họ cố gắng tuyên bố rõ ràng loại rào cản bộ nhớ và đó là không chính xác, đặc biệt là trên các nền tảng sẽ không giúp họ hiểu sai về trật tự bộ nhớ vì chúng yếu hơn trong tự nhiên.

Cuối cùng, hãy nhớ với tình hình của bạn # 4 liên quan đến một mutex rằng có hai điều khác nhau mà cần phải xảy ra ở đây:

  1. Trình biên dịch không được phép để sắp xếp lại hoạt động trên mutex và phần quan trọng (đặc biệt trong trường hợp trình biên dịch tối ưu hóa)
  2. Phải có hàng rào bộ nhớ cần thiết được tạo (tùy thuộc vào nền tảng) duy trì trạng thái nơi tất cả các cửa hàng được hoàn thành trước phần quan trọng và đọc biến mutex và tất cả các cửa hàng được hoàn thành trước khi thoát khỏi phần quan trọng.

Do theo mặc định, cửa hàng nguyên tử và tải được thực hiện với std::memory_order_seq_cst, sau đó sử dụng nguyên tử cũng sẽ thực hiện các cơ chế thích hợp để đáp ứng các điều kiện # 1 và # 2. Điều đó đang được nói, trong ví dụ đầu tiên của bạn với nguyên tử, tải sẽ thực thi các ngữ nghĩa thu được cho khối, trong khi cửa hàng sẽ thực thi ngữ nghĩa phát hành. Nó sẽ không mặc dù thực thi bất kỳ thứ tự cụ thể bên trong "phần quan trọng" giữa hai hoạt động này mặc dù. Trong ví dụ thứ hai của bạn, bạn có hai phần khác nhau với khóa, mỗi khóa có được ngữ nghĩa. Vì tại một thời điểm nào đó bạn sẽ phải giải phóng các khóa, điều này sẽ giải phóng ngữ nghĩa, sau đó không, hai khối mã sẽ không tương đương nhau. Trong ví dụ đầu tiên, bạn đã tạo ra một "phần quan trọng" lớn giữa tải và lưu trữ (giả sử tất cả điều này xảy ra trên cùng một luồng). Trong ví dụ thứ hai, bạn có hai phần quan trọng khác nhau.

P.S. Tôi đã tìm thấy tệp PDF sau đặc biệt là bài học, và bạn có thể tìm thấy nó quá: http://www.nwcpp.org/Downloads/2008/Memory_Fences.pdf

+0

Tôi nghĩ rằng (trong # 2) tải và lưu trữ của bạn trước khi phần quan trọng được nhập có thể được chuyển vào phần quan trọng VÀ tải và lưu trữ sau khi CS có thể được chuyển vào CS. Điều này có nghĩa là trình biên dịch vẫn có thể sắp xếp lại (đến một mức độ) các tải không liên quan đến CS và các cửa hàng xung quanh các ranh giới CS. Điều này là do những tải/cửa hàng này không phải là một phần của CS ban đầu. Nhưng không có gì có thể được chuyển từ trước CS sang sau CS .. chỉ vào giữa nó. – SoapBox

+0

Vâng, đó là chính xác, ít nhất là tôi hiểu ý nghĩa của việc có được và phát hành ngữ nghĩa. – Jason

+0

Có thể xây dựng mutex làm việc của riêng bạn ra khỏi bộ hoạt động nguyên tử mà bạn đã đưa ra trong C++ 11 không? – Omnifarious