2011-01-13 24 views
6

Nếu tôi có một số mã trông giống như sau:gợi ý sau trong một môi trường đa luồng

typedef struct { 
    bool some_flag; 

    pthread_cond_t c; 
    pthread_mutex_t m; 
} foo_t; 

// I assume the mutex has already been locked, and will be unlocked 
// some time after this function returns. For clarity. Definitely not 
// out of laziness ;) 
void check_flag(foo_t* f) { 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
} 

Có điều gì trong tiêu chuẩn C ngăn ngừa một ưu từ viết lại check_flag như:

void check_flag(foo_t* f) { 
    bool cache = f->flag; 
    while(cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 

Trong khác các từ, mã được tạo để theo dõi con trỏ f mỗi lần qua vòng lặp hay trình biên dịch miễn phí để kéo dereference ra?

Nếu số miễn phí để rút ra, có cách nào để ngăn chặn điều này không? Tôi có cần phải rắc một từ khóa dễ bay hơi ở đâu đó không? Nó không thể là tham số của check_flag bởi vì tôi có kế hoạch có các biến khác trong cấu trúc này mà tôi không nhớ trình biên dịch tối ưu hóa như thế này.

Tôi có thể phải nghỉ mát để:

void check_flag(foo_t* f) { 
    volatile bool* cache = &f->some_flag; 
    while(*cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 
+0

+1 để suy nghĩ về loại sự cố này trước khi viết mã luồng bằng thử và lỗi! –

Trả lời

3

Thông thường, bạn nên cố gắng khóa mutex pthread trước khi chờ đợi vào đối tượng tình trạng như việc phát hành pthread_cond_wait gọi mutex (và reacquire nó trước khi trở về). Vì vậy, hàm check_flag của bạn nên được viết lại như thế để phù hợp với ngữ nghĩa trên điều kiện pthread.

void check_flag(foo_t* f) { 
    pthread_mutex_lock(&f->m); 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
    pthread_mutex_unlock(&f->m); 
} 

Liên quan đến vấn đề có hay không trình biên dịch được phép để tối ưu hóa việc đọc của trường flag, answer điều này giải thích nó một cách chi tiết hơn tôi có thể.

Về cơ bản, trình biên dịch biết về ngữ nghĩa của pthread_cond_wait, pthread_mutex_lockpthread_mutex_unlock. Anh ta biết rằng anh ta không thể tối ưu hóa việc đọc bộ nhớ trong những tình huống đó (gọi tới số pthread_cond_wait trong ví dụ này). Không có khái niệm về rào cản bộ nhớ ở đây, chỉ là một kiến ​​thức đặc biệt về chức năng nhất định, và một số quy tắc để tuân theo sự hiện diện của chúng.

Có một điều khác bảo vệ bạn khỏi tối ưu hóa được thực hiện bởi bộ xử lý. Bộ xử lý trung bình của bạn có khả năng sắp xếp lại quyền truy cập bộ nhớ (đọc/ghi) với điều kiện là ngữ nghĩa được bảo toàn và nó luôn làm việc đó (vì nó cho phép tăng hiệu năng). Tuy nhiên, sự ngắt quãng này khi nhiều hơn một bộ xử lý có thể truy cập cùng một địa chỉ bộ nhớ. Một rào cản bộ nhớ chỉ là một hướng dẫn cho bộ xử lý nói với nó rằng nó có thể di chuyển đọc/ghi đã được ban hành trước khi các rào cản và thực hiện chúng sau khi rào cản. Nó đã hoàn thành chúng ngay bây giờ.

+0

Điều đó có nghĩa là trình biên dịch không thể lưu trữ giá trị của 'p-> some_flag' trong sổ đăng ký? Tôi không chắc chắn về tác động của rào cản bộ nhớ. Tâm trí giải thích cho họ một chút? –

+0

Đã chỉnh sửa câu trả lời. Bây giờ có rõ ràng hơn không? –

+0

Vâng, cảm ơn bạn. –

3

Khi được viết, trình biên dịch sẽ tự do lưu vào bộ nhớ cache kết quả khi bạn mô tả hoặc thậm chí theo cách tinh tế hơn - bằng cách đặt nó vào sổ đăng ký. Bạn có thể ngăn chặn việc tối ưu hóa này bằng cách thực hiện biến số volatile. Nhưng điều đó không nhất thiết là đủ - bạn không nên mã hóa nó theo cách này! Bạn nên sử dụng các biến điều kiện theo quy định (khóa, chờ, mở khóa).

Cố gắng làm việc xung quanh thư viện là xấu, nhưng nó trở nên tồi tệ hơn. Có lẽ đọc bài báo của Hans Boehm về chủ đề chung từ PLDI 2005 ("Chủ đề không thể được thực hiện như một thư viện"), hoặc nhiều người trong số follow-on articles (dẫn đến làm việc trên mô hình bộ nhớ C++ đã sửa đổi) sẽ đặt nỗi sợ hãi của Chúa vào bạn và chỉ đạo bạn trở lại thẳng và hẹp :).

+0

Tôi không thể đọc bài báo đó mà không phải trả tiền.Tôi quá nghèo để tìm hiểu =/ –

+1

Liên kết miễn phí thay thế cho báo cáo công nghệ: http://www.hpl.hp.com/techreports/2004/HPL-2004-209.html – EmeryBerger

+0

Cảm ơn bạn đã liên kết. Tôi thích đọc sách của Hans. Họ quá vui vẻ. Tôi sẽ mất một giờ trong cuộc đời. –

7

Trong trường hợp tổng quát, ngay cả khi đa luồng không tham gia và vòng lặp của bạn trông giống như:

void check_flag(foo_t* f) { 
    while(f->flag) 
     foo(&f->c, &f->m); 
} 

trình biên dịch sẽ không thể để bộ nhớ cache thử nghiệm f->flag. Đó là bởi vì trình biên dịch không thể biết có hay không một hàm (như foo() ở trên) có thể thay đổi bất kỳ đối tượng nào f đang trỏ đến.

Trong những trường hợp đặc biệt (foo() được hiển thị cho trình biên dịch, và tất cả các con trỏ truyền cho check_flag() được biết đến không để được aliased hoặc sửa đổi bởi foo()) trình biên dịch có thể có thể để tối ưu hóa việc kiểm tra.

Tuy nhiên, pthread_cond_wait() phải được triển khai theo cách có thể ngăn chặn tối ưu hóa đó.

Xem Does guarding a variable with a pthread mutex guarantee it's also not cached?:

Bạn cũng có thể quan tâm trong câu trả lời Steve Jessop để: Can a C/C++ compiler legally cache a variable in a register across a pthread library call?

Nhưng làm thế nào đến nay bạn có muốn để có những vấn đề đặt ra bởi giấy Boehm trong công việc của riêng bạn là tùy thuộc vào bạn. Theo như tôi có thể nói, nếu bạn muốn đứng lên mà pthreads không/không thể đảm bảo, thì bạn đang trong thực tế lấy đứng rằng pthreads là vô ích (hoặc ít nhất là không cung cấp bảo đảm an toàn, mà Tôi nghĩ rằng bằng cách giảm có cùng một kết quả). Trong khi điều này có thể đúng theo nghĩa hẹp nhất (như được đề cập trong bài báo), nó cũng có thể không phải là một câu trả lời hữu ích. Tôi không chắc chắn bạn có tùy chọn nào khác ngoài tùy chọn pthread trên nền tảng dựa trên Unix.

+0

Tôi ước tôi có thể chấp nhận hai câu trả lời, nhưng tôi không thể. Tôi vừa chọn người có người đại diện thấp nhất. Họ cũng không kém phần hữu ích! –

+1

Đây là câu trả lời hay nhất, nhưng tôi thích lý do của OP để chấp nhận câu trả lời khác. :-) Đối với những gì nó có giá trị, chức năng đồng bộ hóa pthread được quy định là rào cản bộ nhớ đầy đủ. Làm thế nào điều này được thực hiện là không có kinh doanh của ứng dụng; nó chỉ được đảm bảo để hoạt động. –

+0

"_pthread_cond_wait() phải được thực hiện theo cách có thể ngăn chặn tối ưu hóa đó._" Tôi tò mò về cách 'pthread_cond_wait' có thể được triển khai hợp lý theo cách cho phép tối ưu hóa (không chính xác)! – curiousguy

1

Dễ bay hơi là vì mục đích này. Dựa vào trình biên dịch để biết về thực hành mã hóa pthread có vẻ như một chút hạt cho tôi, mặc dù; trình biên dịch khá thông minh trong những ngày này. Trong thực tế, trình biên dịch có thể thấy rằng bạn đang lặp để thử nghiệm một biến và sẽ không lưu nó trong sổ đăng ký vì lý do đó, không phải vì nó thấy bạn sử dụng pthreads. Chỉ cần sử dụng dễ bay hơi nếu bạn thực sự quan tâm.

Loại lưu ý nhỏ buồn cười. Chúng tôi có VOLATILE #define hoặc là "dễ bay hơi" (khi chúng tôi cho rằng lỗi đó không thể là mã của chúng tôi ...) hoặc để trống. Khi chúng tôi nghĩ rằng chúng tôi có một vụ tai nạn do trình tối ưu hóa giết chết chúng tôi, chúng tôi #define nó "dễ bay hơi" mà đặt dễ bay hơi ở phía trước của gần như tất cả mọi thứ. Sau đó, chúng tôi sẽ kiểm tra xem sự cố có bị biến mất hay không. Cho đến nay ... các lỗi đã được các nhà phát triển và không phải là trình biên dịch! ai đã nghĩ thế !? Chúng tôi đã phát triển một thư viện luồng "non locking" và "non blocking" hiệu suất cao. Chúng tôi có một nền tảng thử nghiệm mà búa nó đến điểm của hàng ngàn chủng tộc mỗi giây. Vì vậy, giá vé, chúng tôi chưa bao giờ phát hiện ra một vấn đề cần biến động! Cho đến nay gcc chưa bao giờ lưu trữ một biến chia sẻ trong sổ đăng ký. yah ... chúng tôi cũng ngạc nhiên. Chúng tôi vẫn đang chờ đợi cơ hội của chúng tôi để sử dụng dễ bay hơi!

+0

Tôi thích câu chuyện =) có một bài viết. –

+0

"_compilers là khá thông minh những ngày này._" Khi nào trình biên dịch giả định rằng một cuộc gọi đến một hàm không có hiệu lực trên bất cứ điều gì? – curiousguy

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