2012-08-08 32 views
6

Tôi có một ứng dụng đa luồng tạo 48 chủ đề mà tất cả đều cần truy cập vào một thuộc tính chung (stl :: map). Bản đồ sẽ chỉ được ghi khi chủ đề bắt đầu và phần còn lại của bản đồ sẽ được đọc từ đó. Điều này có vẻ giống như trường hợp sử dụng hoàn hảo cho một pthread_rw_lock, và tất cả dường như hoạt động tốt.Có bao nhiêu người đọc đồng thời một pthread_rwlock có?

Tôi đã chạy qua một lỗi seg hoàn toàn không liên quan và bắt đầu phân tích lõi. Sử dụng gdb, tôi thực hiện lệnh info threads và khá ngạc nhiên về kết quả. Tôi quan sát thấy rằng một số chủ đề đã thực sự đọc từ bản đồ như mong đợi, nhưng phần lạ là một số chủ đề đã bị chặn trong pthread_rwlock_rdlock() chờ đợi trên rw_lock.

Đây là stack trace cho một thread đang chờ đợi trên khóa:

#0 0xffffe430 in __kernel_vsyscall() 
#1 0xf76fe159 in __lll_lock_wait() from /lib/libpthread.so.0 
#2 0xf76fab5d in pthread_rwlock_rdlock() from /lib/libpthread.so.0 
#3 0x0804a81a in DiameterServiceSingleton::getDiameterService(void*)() 

Với rất nhiều chủ đề, nó khó có thể nói bao nhiêu người đã đọc và bao nhiêu đã bị chặn, nhưng tôi không hiểu tại sao bất kỳ số nào chủ đề sẽ bị chặn đang chờ đọc, xem xét các chủ đề khác đã đọc.

Vì vậy, đây là câu hỏi của tôi: Tại sao một số chủ đề bị chặn chờ đợi để đọc một rw_lock, khi các chủ đề khác đã đọc từ nó? Nó xuất hiện như thể có một giới hạn về số lượng các chủ đề có thể đọc đồng thời.

Ive đã xem các chức năng pthread_rwlock_attr_t và không thấy bất kỳ điều gì có liên quan.

Hệ điều hành Linux, SUSE 11.

Đây là mã có liên quan:

{ 
    pthread_rwlock_init(&serviceMapRwLock_, NULL); 
} 

// This method is called for each request processed by the threads 
Service *ServiceSingleton::getService(void *serviceId) 
{ 
    pthread_rwlock_rdlock(&serviceMapRwLock_); 
    ServiceMapType::const_iterator iter = serviceMap_.find(serviceId); 
    bool notFound(iter == serviceMap_.end()); 
    pthread_rwlock_unlock(&serviceMapRwLock_); 

    if(notFound) 
    { 
    return NULL; 
    } 

    return iter->second; 
} 

// This method is only called when the app is starting 
void ServiceSingleton::addService(void *serviceId, Service *service) 
{ 
    pthread_rwlock_wrlock(&serviceMapRwLock_); 
    serviceMap_[serviceId] = service; 
    pthread_rwlock_unlock(&serviceMapRwLock_); 
} 

Cập nhật:

Như đã đề cập trong các ý kiến ​​của MarkB, nếu tôi đã thiết lập pthread_rwlockattr_getkind_np() để ưu tiên cho các nhà văn, và có một nhà văn bị chặn chờ đợi, sau đó hành vi được quan sát sẽ có ý nghĩa. Nhưng, Im sử dụng giá trị mặc định mà tôi tin là ưu tiên cho người đọc. Tôi vừa xác minh rằng có không chủ đề bị chặn đang chờ để viết. Tôi cũng cập nhật mã theo gợi ý của @Shahbaz trong các nhận xét và nhận được kết quả tương tự.

+3

Bạn * chắc chắn * rằng không có khóa chặn người viết nào? –

+0

@MarkB Đó là một câu hỏi tuyệt vời! Nhưng doesnt mà phụ thuộc vào pthread_rwlockattr_getkind_np() mà tôi havent gọi? Im không chắc chắn nếu bất kỳ chủ đề đang chờ để viết, nhưng họ không nên kể từ đó chỉ nên xảy ra ở đầu. Tôi sẽ phải kiểm tra mặc dù. – Brady

+0

@MarkB, điều gì sẽ ảnh hưởng đến điều gì nếu một người viết đang đợi và tôi đã không đặt pthread_rwlockattr_getkind_np()? Theo tôi hiểu, người viết có thể bị bỏ đói nếu có người đọc liên tục, đúng không? – Brady

Trả lời

6

Bạn chỉ quan sát thấy các vấn đề về hiệu suất vốn có liên quan đến việc có khóa. Phải mất một thời gian, và bạn chỉ tình cờ bắt những sợi chỉ ở giữa nó. Điều này đặc biệt đúng khi hoạt động được khóa bảo vệ có thời gian rất ngắn.

Edit: Reading nguồn, glibc sử dụng lll_lock để bảo vệ bộ phận quan trọng trong thư viện pthread riêng cấu trúc dữ liệu của nó. Các pthread_rwlock_rdlock kiểm tra một số cờ và quầy tăng, do đó, nó làm những việc trong khi giữ một khóa. Khi đã xong, khóa sẽ được giải phóng với lll_unlock.

Để minh họa, tôi đã thực hiện một quy trình ngắn ngủ sau khi có được rwlock. Các chủ đề chính chờ đợi cho họ để kết thúc. Nhưng trước khi chờ đợi, nó in đồng thời đạt được bằng các chủ đề.

enum { CONC = 50 }; 

pthread_rwlock_t rwlock; 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 
unsigned count; 

void *routine(void *arg) 
{ 
    int *fds = static_cast<int *>(arg); 
    pthread_rwlock_rdlock(&rwlock); 
    pthread_mutex_lock(&mutex); 
    ++count; 
    if (count == CONC) pthread_cond_signal(&cond); 
    pthread_mutex_unlock(&mutex); 
    sleep(5); 
    pthread_rwlock_unlock(&rwlock); 
    pthread_t self = pthread_self(); 
    write(fds[1], &self, sizeof(self)); 
    return 0; 
} 

Và chờ đợi chủ đề chính cho bộ đếm đạt 50:

int main() 
{ 
    int fds[2]; 
    pipe(fds); 
    pthread_rwlock_init(&rwlock, 0); 
    pthread_mutex_lock(&mutex); 
    for (int i = 0; i < CONC; i++) { 
     pthread_t tid; 
     pthread_create(&tid, NULL, routine, fds); 
    } 
    while (count < CONC) pthread_cond_wait(&cond, &mutex); 
    pthread_mutex_unlock(&mutex); 
    std::cout << "count: " << count << std::endl; 
    for (int i = 0; i < CONC; i++) { 
     pthread_t tid; 
     read(fds[0], &tid, sizeof(tid)); 
     pthread_join(tid, 0); 
    } 
    pthread_rwlock_destroy(&rwlock); 
    pthread_exit(0); 
} 

Edit: Giản ví dụ sử dụng C++ 11 chủ đề hỗ trợ:

enum { CONC = 1000 }; 
std::vector<std::thread> threads; 

pthread_rwlock_t rwlock; 
std::mutex mutex; 
std::condition_variable cond; 
unsigned count; 

void *routine(int self) 
{ 
    pthread_rwlock_rdlock(&rwlock); 
    { std::unique_lock<std::mutex> lk(mutex); 
     if (++count == CONC) cond.notify_one(); } 
    sleep(5); 
    pthread_rwlock_unlock(&rwlock); 
    return 0; 
} 

int main() 
{ 
    pthread_rwlock_init(&rwlock, 0); 
    { std::unique_lock<std::mutex> lk(mutex); 
     for (int i = 0; i < CONC; i++) { 
      threads.push_back(std::thread(routine, i)); 
     } 
     cond.wait(lk, [](){return count == CONC;}); } 
    std::cout << "count: " << count << std::endl; 
    for (int i = 0; i < CONC; i++) { 
     threads[i].join(); 
    } 
    pthread_rwlock_destroy(&rwlock); 
    pthread_exit(0); 
} 
+0

Tôi cập nhật câu hỏi của mình bằng dấu vết ngăn xếp của chuỗi đang chờ trên khóa đọc.Bạn có nghĩa là mặc dù thread nằm trong '__lll_lock_wait()' mà nó không thực sự chờ đợi/bị chặn, nhưng nó nằm trong hàm để có khóa đọc? Nếu vậy, đó là một tên chức năng không may, sẽ không là người đầu tiên mặc dù :) – Brady

+0

@Có. Tôi không có các nguồn trước mặt để xác nhận, nhưng tôi nghi ngờ điều này tương ứng với điểm nhập cuộc gọi hệ thống cho 'glibc' để đến hạt nhân. – jxh

+0

Điều này sẽ có ý nghĩa. Không phải là tôi không tin bạn :) nhưng Im tìm kiếm mã nguồn để xác minh nó. Tôi sẽ cho bạn biết khi tôi tìm thấy nó, cảm ơn! – Brady

3

Là một lưu ý phụ, mã được đăng ở trên bị hỏng. Bạn không thể truy cập iter-> second trong phần rw_lock'd, bởi vì ngay sau khi bạn mở khóa rw_lock, một nhà văn có thể loại bỏ bất kỳ phần tử nào trên bản đồ, do đó làm mất hiệu lực bất kỳ trình lặp nào trên đó.

Tôi biết bạn không làm điều này trong trường hợp của bạn vì bạn không viết bất cứ điều gì trong quá trình bắt đầu thực hiện chương trình, nhưng vẫn còn, nó đáng nhắc đến.

Ngoài ra, như là một lưu ý phụ, vì hành vi mô tả của bạn có vẻ như được sắp xếp theo thứ tự (các nhà văn viết bản đồ vào lúc bắt đầu, sau đó người đọc đọc bản đồ "chỉ đọc"), có lẽ bạn nên viết nó như thế này:

int writerDoneWithMap = 0; 
// pthread_cond & mutex init here 

// The writer write to the map here 

// Then they signal the reader that they are done with it 
while (!__sync_bool_compare_and_swap(&writerDoneWithMap, 1, writerDoneWithMap)); 
pthread_cond_broadcast here 


// The readers will then simply do this: 
while (!writerDoneWithMap) 
{ 
    // pthread_cond_wait here 
} 
// Read the data without locks. 

Đoạn mã trên tránh bất kỳ khóa trên các độc giả nếu tác giả đã hoàn thành việc điền bản đồ, và trong trường hợp họ không có, sau đó bạn nghỉ mát để ngăn split gia pthread_cond/mutex điển hình. Đoạn mã trên là đúng nếu và chỉ khi bạn có người đọc THEN (nhưng đó là những gì bạn đã nói), nếu không nó sẽ thất bại.

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