2011-08-19 27 views
15

xem xét mã nguồn sau đây, mà là hoàn toàn POSIX compliant:Làm thế nào để làm cho pthread_cond_timedwait() mạnh mẽ chống lại các thao tác đồng hồ hệ thống?

#include <stdio.h> 
#include <limits.h> 
#include <stdint.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <sys/time.h> 

int main (int argc, char ** argv) { 
    pthread_cond_t c; 
    pthread_mutex_t m; 
    char printTime[UCHAR_MAX]; 

    pthread_mutex_init(&m, NULL); 
    pthread_cond_init(&c, NULL); 

    for (;;) { 
     struct tm * tm; 
     struct timeval tv; 
     struct timespec ts; 

     gettimeofday(&tv, NULL); 

     printf("sleep (%ld)\n", (long)tv.tv_sec); 
     sleep(3); 

     tm = gmtime(&tv.tv_sec); 
     strftime(printTime, UCHAR_MAX, "%Y-%m-%d %H:%M:%S", tm); 
     printf("%s (%ld)\n", printTime, (long)tv.tv_sec); 

     ts.tv_sec = tv.tv_sec + 5; 
     ts.tv_nsec = tv.tv_usec * 1000; 

     pthread_mutex_lock(&m); 
     pthread_cond_timedwait(&c, &m, &ts); 
     pthread_mutex_unlock(&m); 
    } 
    return 0; 
} 

In ngày hệ thống hiện tại mỗi 5 giây, tuy nhiên, nó hoạt động giấc ngủ của 3 giây giữa nhận được thời gian hệ thống hiện tại (gettimeofday) và tình trạng chờ đợi (pthread_cond_timedwait).

Ngay sau khi in "ngủ (...)", hãy thử đặt đồng hồ hệ thống hai ngày vào quá khứ. Chuyện gì xảy ra? Vâng, thay vì chờ thêm 2 giây nữa về điều kiện như thường lệ, pthread_cond_timedwait hiện đang đợi hai ngày và 2 giây.

Làm cách nào để khắc phục điều đó?
Làm cách nào để tôi có thể viết mã tuân thủ POSIX, điều đó không bị ngắt khi người dùng thao tác đồng hồ hệ thống?

Hãy nhớ rằng đồng hồ hệ thống có thể thay đổi ngay cả khi không tương tác với người dùng (ví dụ: khách hàng NTP có thể tự động cập nhật đồng hồ mỗi ngày một lần). Đặt đồng hồ vào tương lai không thành vấn đề, nó sẽ chỉ khiến giấc ngủ thức dậy sớm, thường không có vấn đề gì và bạn có thể dễ dàng "phát hiện" và xử lý cho phù hợp, nhưng đặt đồng hồ vào quá khứ (ví dụ chạy trong tương lai, NTP phát hiện ra và sửa nó) có thể gây ra một vấn đề lớn.

PS:
Không phải pthread_condattr_setclock() cũng không CLOCK_MONOTONIC tồn tại trên hệ thống của tôi. Đó là bắt buộc đối với đặc tả POSIX 2008 (một phần của "Cơ sở") nhưng hầu hết các hệ thống vẫn chỉ tuân theo đặc tả POSIX 2004 tính đến hôm nay và trong đặc tả POSIX 2004 hai tùy chọn này là tùy chọn (Advanced Realtime Extension).

+2

Tôi rất khó chịu vì hàm này có cấu trúc 'timespec' và sử dụng thời gian tuyệt đối. Tôi nghĩ rằng tôi sẽ sử dụng phương thức 'pipe()'/'select()' để thực hiện nó. – mpontillo

Trả lời

4

Thú vị, tôi đã không gặp phải những hành vi trước, nhưng sau đó một lần nữa, tôi không có thói quen mucking về với thời gian hệ thống của tôi rằng có rất nhiều :-)

Giả sử bạn đang làm điều đó trong một lý do hợp lệ, một giải pháp có thể (mặc dù kludgy) là có một luồng khác có mục đích duy nhất là định kỳ khởi động biến điều kiện để đánh thức bất kỳ luồng nào bị ảnh hưởng.

Nói cách khác, một cái gì đó như:

while (1) { 
    sleep (10); 
    pthread_cond_signal (&condVar); 
} 

Mã của bạn đó là chờ đợi cho các biến điều kiện để được đá nên được kiểm tra vị của nó anyway (để chăm sóc wakeups giả mạo) vì vậy đây không nên có bất kỳ tác động bất lợi thực sự lên chức năng.

Đó là một cú đánh hiệu suất nhỏ nhưng cứ 10 giây một lần lại không quá nhiều vấn đề. Nó chỉ thực sự có nghĩa là để chăm sóc các tình huống mà (vì lý do gì) chờ đợi thời gian của bạn sẽ được chờ đợi một thời gian dài.


Một khả năng khác là thiết kế lại ứng dụng của bạn để bạn không cần phải chờ đợi theo thời gian nào cả.

Trong trường hợp các chủ đề cần phải được đánh thức vì lý do nào đó, nó luôn luôn bằng một chuỗi khác hoàn toàn có khả năng đá biến điều kiện để đánh thức một (hoặc phát sóng để đánh thức rất nhiều).

Điều này rất giống với chủ đề đá tôi đã đề cập ở trên nhưng nhiều hơn như là một phần không thể thiếu trong kiến ​​trúc của bạn hơn là một bu-lông.

3

Bạn có thể bảo vệ mã của mình chống lại sự cố này. Một cách dễ dàng là có một luồng có mục đích duy nhất là xem đồng hồ hệ thống. Bạn giữ một danh sách liên kết toàn cầu của các biến điều kiện và nếu chuỗi trình theo dõi đồng hồ thấy một bước nhảy đồng hồ hệ thống, nó sẽ phát mọi biến điều kiện trong danh sách. Sau đó, bạn chỉ cần quấn pthread_cond_initpthread_cond_destroy bằng mã để thêm/xóa biến điều kiện vào/khỏi danh sách được liên kết toàn cầu. Bảo vệ danh sách liên kết bằng một mutex.

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