2012-06-15 17 views
13

Tôi đang học về đồng thời C++ 11, nơi mà kinh nghiệm trước đây duy nhất của tôi với các nguyên tắc đồng thời trong lớp Hệ điều hành cách đây sáu năm, vì vậy hãy nhẹ nhàng, nếu bạn có thể.C++ 11 std :: condition_variable: chúng ta có thể chuyển khóa của chúng tôi trực tiếp đến chuỗi được thông báo không?

Trong C++ 11, chúng ta có thể viết

std::mutex m; 
std::condition_variable cv; 
std::queue<int> q; 

void producer_thread() { 
    std::unique_lock<std::mutex> lock(m); 
    q.push(42); 
    cv.notify_one(); 
} 

void consumer_thread() { 
    std::unique_lock<std::mutex> lock(m); 
    while (q.empty()) { 
     cv.wait(lock); 
    } 
    q.pop(); 
} 

này hoạt động tốt, nhưng tôi bị xúc phạm bởi sự cần thiết phải quấn cv.wait trong một vòng lặp. Lý do chúng ta cần vòng lặp là rõ ràng với tôi:

Consumer (inside wait())  Producer   Vulture 

release the lock 
sleep until notified 
           acquire the lock 
           I MADE YOU A COOKIE 
           notify Consumer 
           release the lock 
                acquire the lock 
                NOM NOM NOM 
                release the lock 
acquire the lock 
return from wait() 
HEY WHERE'S MY COOKIE        I EATED IT 

Bây giờ, tôi tin rằng một trong những điều thú vị về unique_lock được rằng chúng ta có thể vượt qua nó xung quanh, phải không? Vì vậy, nó sẽ thực sự thanh lịch nếu chúng ta có thể làm điều này thay vì:

Consumer (inside wait())  Producer 

release the lock 
sleep until notified 
           acquire the lock 
           I MADE YOU A COOKIE 
           notify and yield(passing the lock) 
wake(receiving the lock) 
return from wait() 
YUM 
release the lock 

Bây giờ không có cách nào cho thread Vulture để swoop trong, bởi vì mutex vẫn bị khóa tất cả các cách I MADE YOU A COOKIE-YUM. Ngoài ra, nếu notify() yêu cầu bạn vượt qua khóa, đó là cách tốt để đảm bảo rằng mọi người thực sự khóa mutex trước khi gọi notify() (xem Signalling a condition variable (pthreads)).

Tôi khá chắc chắn rằng C++ 11 không có bất kỳ triển khai chuẩn nào cho thành ngữ này. Lý do lịch sử cho điều đó (là nó chỉ là pthreads đã không làm điều đó? Và sau đó tại sao là rằng)? Có một lý do kỹ thuật mà một coder C++ mạo hiểm không thể thực hiện thành ngữ này trong tiêu chuẩn C++ 11, gọi nó có lẽ là my_better_condition_variable?

Tôi cũng có cảm giác mơ hồ rằng có lẽ tôi đang phát minh lại các ẩn dụ, nhưng tôi không nhớ đủ từ trường để biết liệu đó có chính xác hay không.

+3

Erm, nếu bạn không muốn có một số chủ đề để ăn cookie , tại sao bạn thậm chí còn chạy nó? –

+2

Điểm tốt, giọng điệu của câu hỏi của tôi hơi thiên vị đối với Kền kền. :) Xem, Người tiêu dùng đang có một giấc mơ đẹp. Anh ta sẵn sàng thức dậy với bánh quy và sữa, nhưng nếu anh tỉnh dậy và thấy * không * cookie, anh ta sẽ buồn. Vì vậy, nhà sản xuất có thể cung cấp cookie cho Vulture, nhưng không phải * không sao cho Nhà sản xuất đánh thức Người tiêu dùng bằng cách lắc và hét lên "COOKIE TIME!" và sau đó sẽ "oh, tiếc, Vulture ăn cookie của bạn trong khi tôi đã lắc bạn." Đó không phải là mát mẻ, nhà sản xuất. Đó không phải là mát mẻ. – Quuxplusone

+0

Điều đó không có ý nghĩa. Bạn không thể mong đợi nhiều chủ đề không liên quan không biết các chủ đề khác truy cập cùng một dữ liệu được chia sẻ để hoạt động chính xác. Làm thế nào Vulture bao giờ ăn bất kỳ cookie với giải pháp đề xuất của bạn? –

Trả lời

10

Câu trả lời cuối cùng là vì pthreads không làm điều đó. C++ là một ngôn ngữ đóng gói chức năng của hệ điều hành. C++ không phải là hệ điều hành hoặc nền tảng. Và do đó, nó đóng gói các chức năng hiện có của các hệ điều hành như linux, unix và windows.

Tuy nhiên, pthread cũng có lý do chính đáng cho hành vi này. Từ kỹ thuật Open Group Base:

Hiệu quả là hơn một thread có thể trở lại từ cuộc gọi của nó để pthread_cond_wait() hoặc pthread_cond_timedwait() như là một kết quả của một cuộc gọi đến pthread_cond_signal(). Hiệu ứng này được gọi là "giả mạo đánh thức".Lưu ý rằng tình huống tự điều chỉnh ở chỗ số của các chủ đề được đánh thức là hữu hạn; ví dụ: tiếp theo chuỗi để gọi pthread_cond_wait() sau chuỗi các sự kiện ở trên khối.

Trong khi vấn đề này có thể được giải quyết, sự mất mát hiệu quả cho một điều kiện rìa mà chỉ xảy ra hiếm khi là không thể chấp nhận, đặc biệt là cho rằng người ta phải kiểm tra vị liên kết với một điều kiện biến anyway. Khắc phục sự cố này sẽ làm giảm mức độ đồng thời trong khối xây dựng cơ bản này một cách không cần thiết cho tất cả các hoạt động đồng bộ hóa cấp cao hơn .

Một lợi ích bổ sung của việc cho phép đánh thức giả mạo là các ứng dụng được buộc phải mã một vòng lặp thử nghiệm vị ngữ xung quanh điều kiện chờ. Điều này cũng làm cho ứng dụng chịu đựng tình trạng thừachương trình phát sóng hoặc tín hiệu trên cùng một biến điều kiện có thể được mã hoá trong một số phần khác của ứng dụng. Các ứng dụng kết quả là do đó mạnh mẽ hơn. Do đó, IEEE Std 1003.1-2001 tài liệu rõ ràng rằng các lần đánh thức giả mạo có thể xảy ra.

Vì vậy, về cơ bản yêu cầu bồi thường là bạn có thể xây dựng my_better_condition_variable trên đầu trang của một biến trạng pthreads (hoặc std::condition_variable) khá dễ dàng và không có hình phạt hiệu quả. Tuy nhiên, nếu chúng tôi đặt my_better_condition_variable ở cấp cơ sở, thì những khách hàng không cần chức năng của my_better_condition_variable sẽ phải trả tiền cho nó.

Triết lý này đưa thiết kế nhanh nhất, nguyên thủy nhất ở cuối ngăn xếp, với mục đích là những thứ tốt hơn/chậm hơn có thể được xây dựng trên đầu trang, chạy trong suốt C++ lib. Và nơi mà C++ lib không tuân theo triết lý này, khách hàng thường (và đúng) bị kích thích.

+0

R. Martinho Fernandes và tôi đã có một cuộc thảo luận dài đẹp trong trò chuyện, vì vậy tôi không có nhiều câu hỏi chưa được trả lời. pthreads đưa ra quyết định rằng bạn không thể khóa một mutex trong một thread và mở khóa nó trong một thread khác; do đó nó không thể vượt qua một khóa trên một mutth pthreads từ nhà sản xuất để người tiêu dùng trong ví dụ của tôi (và 'std :: mutex' được mô hình hóa trên pthreads). Do đó, việc xây dựng 'my_better_condition_variable' sẽ đòi hỏi nhiều công việc hơn tôi nghĩ, bởi vì bạn muốn * trước tiên * phải xây dựng' my_unsafer_mutex'. Nó sẽ là một thư viện toàn bộ chủ đề song song và không tương thích với thư viện C++ 11. – Quuxplusone

+1

"* Vì vậy, về cơ bản yêu cầu bồi thường là bạn có thể xây dựng ... khá dễ dàng và không có hình phạt hiệu suất. *" Và sau đó "* Tuy nhiên nếu chúng ta đặt ... sẽ phải trả tiền cho nó anyway. *" Nếu không có hình phạt hiệu suất , vậy thì họ trả tiền chính xác là gì? – ildjarn

+1

@ildjarn: Như được giải thích trong lý do posix, sự cần thiết cho vòng lặp xung quanh sự chờ đợi là sự chờ đợi có thể trở lại giả (tức là không được báo hiệu). Yêu cầu bồi thường là nó là ít tốn kém để thực hiện một biến điều kiện có thể trở về giả từ một chờ đợi, như trái ngược với một trong đó không. Và hầu hết các khách hàng của biến điều kiện sẽ bỏ qua những lần đánh thức giả mạo. Vì vậy, những khách hàng đó không phải trả tiền cho một biến điều kiện được đảm bảo không trả về giả. –

3

Thậm chí nếu bạn có thể làm điều này, thông số C++ 11 cho phép cv.wait() để bỏ chặn giả (để tính toán cho nền tảng có hành vi đó). Vì vậy, ngay cả khi không có chủ đề kền kền (đặt sang một bên các đối số về việc có hay không họ nên tồn tại), các chủ đề tiêu dùng không thể mong đợi có được một cookie chờ đợi, và vẫn phải kiểm tra.

5

Nếu bạn không muốn viết vòng lặp bạn có thể sử dụng overload mà mất vị thay vì:

cv.wait(lock, [&q]{ return !q.is_empty(); }); 

Nó được định nghĩa là tương đương với các vòng lặp, vì vậy nó hoạt động giống như mã gốc .

+0

(Đối với hồ sơ.) Phản đối của tôi đối với giải pháp này là nó vẫn có vòng lặp bỏ phiếu, ẩn bên trong việc thực hiện 'wait'. Nếu mã của tôi sẽ lặp lại, tôi thực sự thích vòng lặp 'while' rõ ràng hơn. – Quuxplusone

+2

@Quuxplusone: "Tương đương với" được xóa rất xa khỏi "được triển khai theo" trong tiêu chuẩn C++. "Tương đương với" giải thích ngữ nghĩa/kết quả dự kiến, nhưng điều đó không ngụ ý rằng có một vòng lặp bỏ phiếu theo nghĩa đen ở đây theo bất kỳ cách nào. – ildjarn

0

Tôi nghĩ rằng đây không phải là an toàn:

void producer_thread() { 
    std::unique_lock<std::mutex> lock(m); 
    q.push(42); 
    cv.notify_one(); 
} 

Bạn vẫn đang giữ khóa khi bạn thông báo cho một thread chờ đợi khóa. Vì vậy, nó có thể là thread khác ngay lập tức tỉnh táo và cố gắng để có được khóa trước khi destructor gọi là sau khicv.notify_one() giải phóng khóa. Điều đó có nghĩa là các chủ đề khác quay trở lại để chờ đợi cuối cùng cho bao giờ hết.

Vì vậy, tôi nghĩ rằng điều này sẽ được mã hóa như:

void producer_thread() { 
    std::unique_lock<std::mutex> lock(m); 
    q.push(42); 
    lock.unlock(); 
    cv.notify_one(); 
} 

hoặc nếu bạn không thích để mở khóa bằng tay như

void producer_thread() { 
    { 
     std::unique_lock<std::mutex> lock(m); 
     q.push(42); 
    } 
    cv.notify_one(); 
} 
+0

có đúng không? - - – dani