2011-08-22 37 views
6

GCC cung cấp một bộ tốt đẹp của built-in functions cho các hoạt động nguyên tử. Và đang sử dụng hệ điều hành MacOS hoặc iOS, ngay cả Apple cũng cung cấp nice set of atomic functions. Tuy nhiên, tất cả các chức năng này thực hiện một thao tác, ví dụ: một phép cộng/trừ, một phép toán logic (AND/OR/XOR) hoặc so sánh và đặt/so sánh và trao đổi. Những gì tôi đang tìm kiếm là cách để chỉ định/đọc một cách nguyên tắc giá trị int, như:Đọc/ghi nguyên tử giá trị int w/o hoạt động bổ sung trên giá trị int của chính nó

int a; 
/* ... */  
a = someVariable; 

Đó là tất cả. a sẽ được đọc bởi một chuỗi khác và điều quan trọng là a có giá trị cũ hoặc giá trị mới của nó. Thật không may là tiêu chuẩn C không đảm bảo rằng việc gán hoặc đọc một giá trị là một hoạt động nguyên tử. Tôi nhớ rằng tôi đã từng đọc ở đâu đó, việc viết hoặc đọc một giá trị cho biến số int được đảm bảo là nguyên tử trong GCC (bất kể kích thước của int) nhưng tôi đã tìm kiếm ở khắp mọi nơi trên trang chủ GCC và tôi không thể tìm thấy câu lệnh này nữa (có thể nó đã bị xóa).

Tôi không thể sử dụng sig_atomic_t vì sig_atomic_t không có kích thước đảm bảo và kích thước cũng có thể có kích thước khác với int.

Vì chỉ có một chủ đề sẽ bao giờ "viết" một giá trị cho a, trong khi cả hai chủ đề sẽ "đọc" các giá trị hiện tại của a, tôi không cần phải thực hiện các hoạt động bản thân một cách nguyên tử, ví dụ:

/* thread 1 */ 
someVariable = atomicRead(a); 
/* Do something with someVariable, non-atomic, when done */ 
atomicWrite(a, someVariable); 

/* thread 2 */ 
someVariable = atomicRead(a); 
/* Do something with someVariable, but never write to a */ 

Nếu cả hai luồng sẽ ghi vào a thì tất cả các thao tác sẽ phải là nguyên tử, nhưng theo cách đó, điều này chỉ có thể lãng phí thời gian CPU; và chúng tôi rất ít tài nguyên CPU trong dự án của mình. Cho đến nay chúng tôi sử dụng một mutex xung quanh hoạt động đọc/ghi của a và mặc dù mutex được giữ trong một khoảng thời gian nhỏ như vậy, điều này đã gây ra sự cố (một trong các chủ đề là chuỗi thời gian thực và chặn trên mutex. các ràng buộc thời gian thực của nó, điều này khá tệ).

Tất nhiên tôi có thể sử dụng __sync_fetch_and_add để đọc biến (và chỉ cần thêm "0" vào nó, để không sửa đổi giá trị của nó) và để viết sử dụng __sync_val_compare_and_swap để viết nó (như tôi biết giá trị cũ của nó điều đó sẽ đảm bảo giá trị luôn được trao đổi), nhưng điều này sẽ không làm tăng thêm chi phí không cần thiết?

Trả lời

3

Một __sync_fetch_and_add với một đối số 0 thực sự là lựa chọn tốt nhất nếu bạn muốn tải của bạn được nguyên tử hoạt động như một rào cản bộ nhớ. Tương tự, bạn có thể sử dụng and với 0 hoặc or với -1 để lưu 0 và -1 nguyên tử với hàng rào bộ nhớ. Đối với văn bản, bạn có thể sử dụng __sync_test_and_set (thực sự là một hoạt động xchg) nếu một rào cản "có được" là đủ, hoặc nếu sử dụng Clang bạn có thể sử dụng __sync_swap (đó là một hoạt động xchg với một rào cản đầy đủ).

Tuy nhiên, trong nhiều trường hợp quá mức cần thiết và bạn có thể muốn thêm hàng rào bộ nhớ theo cách thủ công. Nếu bạn không muốn hàng rào bộ nhớ, bạn có thể sử dụng một tải dễ bay hơi để đọc nguyên tử/viết một biến mà được liên kết và không lớn hơn một từ:

#define __sync_access(x) (*(volatile __typeof__(x) *) &(x)) 

(Macro này là một giá trị trái, vì vậy bạn cũng có thể sử dụng nó cho một cửa hàng như __sync_store(x) = 0). Chức năng thực hiện các ngữ nghĩa tương tự như hình thức 11 memory_order_consume C++, nhưng chỉ dưới hai giả định:

  • rằng máy tính của bạn có bộ nhớ đệm chặt chẽ; nếu không, bạn cần một hàng rào bộ nhớ hoặc bộ nhớ cache toàn cầu tuôn ra trước khi tải (hoặc trước lần tải đầu tiên của nhóm).

  • rằng máy của bạn không phải là DEC Alpha. Alpha có ngữ nghĩa rất thoải mái để sắp xếp lại quyền truy cập bộ nhớ, vì vậy bạn cần có hàng rào bộ nhớ sau khi tải (và sau mỗi lần tải trong một nhóm tải). Trên Alpha, macro trên chỉ cung cấp memory_order_relaxed ngữ nghĩa. BTW, các phiên bản đầu tiên của Alpha thậm chí không thể lưu trữ một byte nguyên tử (chỉ một từ, đó là 8 byte).

Trong cả hai trường hợp, __sync_fetch_and_add sẽ hoạt động. Theo như tôi biết, không có máy khác bắt chước Alpha vì vậy không giả định nào nên đặt ra vấn đề trên máy tính hiện tại.

+0

Làm cách nào để biết nó không rộng hơn một từ? Đi theo ISO-C-99, "loại int" nào được định nghĩa là luôn chính xác bằng từ? Theo như tôi có thể nói điều này là không đảm bảo cho bất kỳ loại int; ví dụ. trên một bộ điều khiển vi 8 bit, một int sẽ vẫn là 16 bit, mặc dù kích thước từ thực tế là 8 bit. – Mecki

+0

Một 'sig_atomic_t' nên làm, ngay cả khi nó có nghĩa là cho một cái gì đó hoàn toàn khác nhau (xử lý tín hiệu). Dù sao, tôi không nghĩ rằng backends vi điều khiển thậm chí thực hiện '__sync_fetch_and_add', vì vậy nếu bạn đưa họ vào tài khoản tất cả các cược được tắt. –

+1

'intptr_t' và' uintptr_t' typedefs được đảm bảo là thích hợp để lưu trữ một con trỏ, mà thường là rộng như từ máy. –

2

Đọc/viết có kích thước, liên kết, dễ đọc, có kích thước nguyên tử trên hầu hết các nền tảng. Kiểm tra lắp ráp của bạn sẽ là cách tốt nhất để tìm hiểu xem điều này là đúng trên nền tảng của bạn. Các thanh ghi nguyên tử không thể tạo ra gần như nhiều cấu trúc tự do chờ đợi thú vị như các cơ chế phức tạp hơn như so sánh và trao đổi, đó là lý do tại sao chúng được bao gồm.

Xem http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.56.5659&rank=3 cho lý thuyết.

Về synch_fetch_and_add với đối số 0 - Điều này có vẻ giống như đặt cược an toàn nhất. Nếu bạn lo lắng về hiệu quả, hãy lập cấu hình mã và xem bạn có đáp ứng được các mục tiêu hiệu suất của mình hay không. Bạn có thể là nạn nhân của việc tối ưu hóa sớm.

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