Tại sao pthread_cond_wait có đánh thức giả?


144

Để trích dẫn trang người đàn ông:

Khi sử dụng các biến điều kiện, luôn có một biến vị ngữ Boolean liên quan đến các biến được chia sẻ liên quan đến từng điều kiện chờ đó là đúng nếu luồng nên tiến hành. Đánh thức giả từ các hàm pthread_cond_timedwait () hoặc pthread_cond_wait () có thể xảy ra. Vì trả về từ pthread_cond_timedwait () hoặc pthread_cond_wait () không ngụ ý bất cứ điều gì về giá trị của vị từ này, nên vị từ được đánh giá lại khi trả về như vậy.

Vì vậy, pthread_cond_waitcó thể trở lại ngay cả khi bạn không báo hiệu nó. Thoạt nhìn ít nhất, điều đó có vẻ khá tàn bạo. Nó sẽ giống như một hàm trả lại ngẫu nhiên giá trị sai hoặc trả về ngẫu nhiên trước khi nó thực sự đạt được một tuyên bố trả về thích hợp. Có vẻ như một lỗi lớn. Nhưng thực tế là họ đã chọn ghi lại điều này trong trang người đàn ông thay vì sửa nó dường như cho thấy rằng có một lý do chính đáng tại sao pthread_cond_waitcuối cùng lại thức dậy một cách giả tạo. Có lẽ, có một cái gì đó nội tại về cách thức hoạt động của nó khiến nó không thể được giúp đỡ. Câu hỏi là gì.

Tại sao không pthread_cond_waittrở spuriously? Tại sao nó không thể đảm bảo rằng nó sẽ chỉ thức dậy khi nó được báo hiệu đúng? Bất cứ ai có thể giải thích lý do cho hành vi giả mạo của nó?


5
Tôi tưởng tượng nó có liên quan đến việc quay lại bất cứ khi nào quá trình bắt được tín hiệu. Hầu hết * nixes không khởi động lại cuộc gọi chặn sau khi tín hiệu làm gián đoạn cuộc gọi đó; họ chỉ cần đặt / trả lại mã lỗi cho biết tín hiệu đã xảy ra.
cHao

1
@cHao: mặc dù lưu ý rằng vì các biến điều kiện có các lý do khác để đánh thức giả dù sao, việc xử lý tín hiệu không phải là lỗi đối với pthread_cond_(timed)wait: "Nếu tín hiệu được gửi ... luồng sẽ tiếp tục chờ biến điều kiện như thể nó là không bị gián đoạn, hoặc nó sẽ trả về 0 do đánh thức giả ". Các chức năng chặn khác cho biết EINTRkhi bị gián đoạn bởi tín hiệu (ví dụ read) hoặc được yêu cầu tiếp tục (ví dụ pthread_mutex_lock). Vì vậy, nếu không có lý do nào khác để đánh thức giả, pthread_cond_waitcó thể đã được định nghĩa giống như một trong những lý do đó.
Steve Jessop

4
Một bài viết liên quan trên Wikipedia: Đánh thức giả
Palec


Nhiều chức năng không thể thực hiện hoàn toàn công việc của mình (I / O bị gián đoạn) và các chức năng quan sát có thể nhận được sự kiện không giống như thay đổi thư mục nơi thay đổi bị hủy hoặc hoàn nguyên. Có vấn đề gì vậy?
tò mò

Câu trả lời:


77

Giải thích sau đây được đưa ra bởi David R. Butenhof trong "Lập trình với chủ đề POSIX" (trang 80):

Đánh thức giả có vẻ lạ, nhưng trên một số hệ thống đa bộ xử lý, việc đánh thức điều kiện hoàn toàn có thể dự đoán được có thể làm chậm đáng kể tất cả các hoạt động biến điều kiện.

Trong cuộc thảo luận comp.programming.threads sau đây , ông mở rộng suy nghĩ đằng sau thiết kế:

Patrick Doyle đã viết: 
> Trong bài viết, Tom Payne đã viết: 
>> Kaz Kylheku đã viết: 
>>: Đó là vì các triển khai đôi khi không thể tránh việc chèn 
>>: những đánh thức giả này; nó có thể là tốn kém để ngăn chặn chúng.

>> Nhưng tại sao? Tại sao điều này là rất khó khăn? Ví dụ, chúng ta đang nói về
>> tình huống chờ đợi khi tín hiệu đến? 

> Bạn biết đấy, tôi tự hỏi nếu các nhà thiết kế của pthreads sử dụng logic như thế này: 
> người dùng của các biến điều kiện phải kiểm tra điều kiện khi thoát, dù sao đi nữa, 
> vì vậy chúng tôi sẽ không đặt thêm gánh nặng cho họ nếu chúng tôi cho phép 
> đánh thức giả; và vì nó có thể hiểu được rằng cho phép giả
> đánh thức có thể làm cho việc thực hiện nhanh hơn, nó chỉ có thể giúp nếu chúng ta 
> cho phép họ. 

> Họ có thể không có bất kỳ triển khai cụ thể nào trong tâm trí. 

Bạn thực sự không ở đâu xa, ngoại trừ bạn đã không đẩy nó đủ xa. 

Mục đích là để buộc mã chính xác / mạnh mẽ bằng cách yêu cầu các vòng lặp vị ngữ. Đây là
được thúc đẩy bởi đội ngũ học thuật chính xác có thể chứng minh được trong số các "chủ đề cốt lõi" trong 
nhóm làm việc, mặc dù tôi không nghĩ có ai thực sự không đồng ý với ý định đó 
một khi họ hiểu ý nghĩa của nó 

Chúng tôi theo ý định đó với nhiều cấp độ biện minh. Đầu tiên là
"tôn giáo" bằng cách sử dụng một vòng lặp bảo vệ ứng dụng chống lại sự không hoàn hảo của chính nó 
thực hành mã hóa. Thứ hai là nó không khó để tưởng tượng một cách trừu tượng
máy móc và mã thực hiện có thể khai thác yêu cầu này để cải thiện 
hiệu suất của các hoạt động chờ điều kiện trung bình thông qua tối ưu hóa 
cơ chế đồng bộ hóa. 
/ ------------------ [David.Buten ... @ compaq.com] ------------------ \ 
| Compaq Computer Corporation Kiến trúc sư chủ đề POSIX |
| Sách của tôi: http://www.awl.com/cseng/title/0-201-63392-2/ |
\ ----- [http://home.earthlink.net/~anneart/f Family / dave.html] ----- / 


22
về cơ bản điều này không nói gì. Không có lời giải thích nào được đưa ra ở đây ngoài suy nghĩ ban đầu rằng "nó có thể làm mọi thứ nhanh hơn" nhưng không ai biết làm thế nào hoặc nếu nó làm gì cả.
Bogdan Ionitza

107

Có ít nhất hai điều 'đánh thức giả' có thể có nghĩa là:

  • Một chuỗi bị chặn pthread_cond_waitcó thể trở về từ cuộc gọi mặc dù không có cuộc gọi đến pthread_call_signalhoặc pthread_cond_broadcastvới điều kiện xảy ra.
  • Một chủ đề bị chặn trong pthread_cond_waitlợi nhuận vì một cuộc gọi đến pthread_cond_signalhay pthread_cond_broadcast, tuy nhiên sau reacquiring mutex vị tiềm ẩn được tìm thấy không còn là sự thật.

Nhưng trường hợp sau có thể xảy ra ngay cả khi việc thực hiện biến điều kiện không cho phép trường hợp trước. Hãy xem xét một hàng tiêu dùng sản xuất, và ba chủ đề.

  • Chủ đề 1 vừa xử lý một phần tử và phát hành mutex, và hàng đợi hiện trống. Các luồng đang làm bất cứ điều gì nó làm với phần tử mà nó có được trên một số CPU.
  • Chủ đề 2 cố gắng loại bỏ một phần tử, nhưng thấy hàng đợi trống khi được kiểm tra dưới mutex, các cuộc gọi pthread_cond_waitvà các khối trong cuộc gọi chờ tín hiệu / phát sóng.
  • Chủ đề 3 có được mutex, chèn một phần tử mới vào hàng đợi, thông báo biến điều kiện và giải phóng khóa.
  • Đáp lại thông báo từ luồng 3, luồng 2, đang chờ điều kiện, được lên lịch để chạy.
  • Tuy nhiên, trước khi luồng 2 quản lý để vào CPU và lấy khóa hàng đợi, luồng 1 hoàn thành nhiệm vụ hiện tại của nó và quay trở lại hàng đợi để làm việc nhiều hơn. Nó nhận được khóa hàng đợi, kiểm tra vị ngữ và thấy rằng có công việc trong hàng đợi. Nó tiến hành để loại bỏ vật phẩm mà luồng 3 đã chèn, giải phóng khóa và làm bất cứ điều gì nó làm với vật phẩm mà luồng 3 đã mê hoặc.
  • Bây giờ, luồng 2 được đưa vào CPU và lấy khóa, nhưng khi kiểm tra vị ngữ, nó thấy rằng hàng đợi trống. Chủ đề 1 'lấy trộm' vật phẩm, vì vậy sự thức dậy dường như là giả mạo. Chủ đề 2 cần phải chờ điều kiện một lần nữa.

Vì vậy, vì bạn đã luôn luôn cần kiểm tra vị ngữ trong một vòng lặp, sẽ không có gì khác biệt nếu các biến điều kiện cơ bản có thể có các kiểu đánh thức giả khác.


23
Đúng. Essentialy, đây là những gì xảy ra khi một sự kiện được sử dụng thay vì cơ chế đồng bộ hóa với số đếm. Đáng buồn thay, có vẻ như các semaphores POSIX, (trên Linux dù sao), cũng là đối tượng của sự thức tỉnh spurius. Tôi chỉ thấy hơi lạ là lỗi chức năng cơ bản của các nguyên thủy đồng bộ hóa chỉ được chấp nhận là 'bình thường' và phải được xử lý ở cấp độ người dùng :( Có lẽ, các nhà phát triển sẽ sẵn sàng nếu cuộc gọi hệ thống được ghi lại với phần 'Phân tách giả mạo' hoặc, có lẽ 'Kết nối giả với URL sai' hoặc 'Mở giả mạo tệp sai'.
Martin James

2
Kịch bản phổ biến hơn của "đánh thức giả" rất có thể là hiệu ứng phụ của một cuộc gọi đến pthread_cond_broadcast (). Giả sử bạn có một nhóm gồm 5 chủ đề, hai chủ đề thức dậy để phát sóng và thực hiện công việc. Ba người kia thức dậy và thấy công việc đã được thực hiện. Các hệ thống đa bộ xử lý cũng có thể dẫn đến tín hiệu có điều kiện đánh thức nhiều luồng một cách tình cờ. Mã chỉ kiểm tra lại vị ngữ một lần nữa, thấy trạng thái không hợp lệ và quay trở lại trạng thái ngủ. Trong cả hai trường hợp, kiểm tra vị ngữ giải quyết vấn đề. IMO, nói chung, người dùng không nên sử dụng các biến thể và điều kiện POSIX thô.
CubicleSoft

1
@MartinJames - Làm thế nào về EINTR "giả" cổ điển? Tôi sẽ đồng ý rằng việc liên tục kiểm tra EINTR trong một vòng lặp là một điều khó chịu và làm cho mã khá xấu nhưng các nhà phát triển vẫn làm điều đó để tránh bị phá vỡ ngẫu nhiên.
CubicleSoft

2
@Yola Không, không thể, bởi vì bạn phải khóa một mutex xung quanh pthread_cond_signal/broadcastvà bạn sẽ không thể làm như vậy, cho đến khi mutex được mở khóa bằng cách gọi pthread_cond_wait.
a3f

1
Ví dụ về câu trả lời này rất thực tế và tôi đồng ý rằng kiểm tra các vị ngữ là một ý tưởng tốt. Tuy nhiên, không thể sửa lỗi bằng nhau bằng cách thực hiện bước có vấn đề "luồng 1 hoàn thành nhiệm vụ hiện tại và quay lại hàng đợi để làm việc nhiều hơn" và thay thế bằng "luồng 1 hoàn thành nhiệm vụ hiện tại và quay lại chờ biến điều kiện "? Điều đó sẽ loại bỏ chế độ thất bại được mô tả trong câu trả lời và tôi khá chắc chắn rằng nó sẽ làm cho mã chính xác, trong trường hợp không có đánh thức giả . Có bất kỳ thực hiện thực tế tạo ra đánh thức giả trong thực tế?
Quuxplusone

7

Phần "Nhiều đánh thức bằng tín hiệu điều kiện" trong pthread_cond_signal có một ví dụ về triển khai pthread_cond_wait và pthread_cond_signal liên quan đến đánh thức giả.


2
Tôi nghĩ rằng câu trả lời này là sai, theo như nó đi. Việc triển khai mẫu trên trang đó có triển khai "thông báo cho một người" tương đương với "thông báo cho tất cả"; nhưng nó dường như không tạo ra sự thức tỉnh giả . Cách duy nhất để một chủ đề thức dậy là bằng một số chủ đề khác gọi "thông báo cho tất cả", hoặc bằng một số chủ đề khác gọi điều được dán nhãn- "thông báo cho một" -có-thực sự- "thông báo tất cả".
Quuxplusone

5

Mặc dù tôi không nghĩ rằng nó đã được xem xét tại thời điểm thiết kế, nhưng đây là một lý do kỹ thuật thực tế: Kết hợp với hủy bỏ luồng, có những điều kiện để sử dụng tùy chọn đánh thức "giả", ít nhất là cần thiết, trừ khi bạn sẵn sàng áp đặt các ràng buộc rất mạnh mẽ về loại chiến lược thực hiện nào có thể.

Vấn đề chính là, nếu một luồng có tác dụng hủy trong khi bị chặn pthread_cond_wait, các tác dụng phụ phải như thể nó không tiêu thụ bất kỳ tín hiệu nào trên biến điều kiện. Tuy nhiên, thật khó (và rất hạn chế) để đảm bảo rằng bạn chưa tiêu thụ tín hiệu khi bạn bắt đầu thực hiện hủy và ở giai đoạn này, có thể không thể "đăng lại" tín hiệu cho biến điều kiện, vì bạn có thể trong một tình huống mà người gọi pthread_cond_signalđã được chứng minh là đã phá hủy condvar và giải phóng bộ nhớ mà nó cư trú.

Trợ cấp cho đánh thức giả cho bạn một cách dễ dàng. Thay vì tiếp tục hành động hủy bỏ khi nó đến trong khi bị chặn trên một biến điều kiện, nếu bạn có thể đã sử dụng tín hiệu (hoặc nếu bạn muốn lười biếng, bất kể điều gì), bạn có thể tuyên bố một cảnh báo giả xảy ra thay vào đó, và trở lại với thành công. Điều này hoàn toàn không can thiệp vào hoạt động hủy bỏ, bởi vì một người gọi chính xác sẽ chỉ đơn giản là hành động hủy bỏ chờ xử lý vào lần tiếp theo nó lặp lại và gọi pthread_cond_waitlại.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.