Ví dụ cho boost shared_mutex (nhiều lần đọc / một lần ghi)?


116

Tôi có một ứng dụng đa luồng phải đọc một số dữ liệu thường xuyên và đôi khi dữ liệu đó được cập nhật. Ngay bây giờ mutex giữ quyền truy cập vào dữ liệu đó an toàn, nhưng nó đắt tiền vì tôi muốn nhiều luồng có thể đọc đồng thời và chỉ khóa chúng khi cần cập nhật (luồng cập nhật có thể đợi các luồng khác hoàn thành) .

Tôi nghĩ đây là những gì boost::shared_mutexphải làm, nhưng tôi không rõ về cách sử dụng nó và chưa tìm thấy một ví dụ rõ ràng.

Có ai có một ví dụ đơn giản mà tôi có thể sử dụng để bắt đầu không?


Ví dụ của 1800 INFORMATION là đúng. Xem thêm bài viết này: Có gì mới trong Boost Threads .
Assaf Lavie

Có thể có bản sao của Khóa trình đọc / ghi trong C ++
cHao

Câu trả lời:


102

Có vẻ như bạn sẽ làm điều gì đó như sau:

boost::shared_mutex _access;
void reader()
{
  // get shared access
  boost::shared_lock<boost::shared_mutex> lock(_access);

  // now we have shared access
}

void writer()
{
  // get upgradable access
  boost::upgrade_lock<boost::shared_mutex> lock(_access);

  // get exclusive access
  boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
  // now we have exclusive access
}

7
Đây là lần đầu tiên tôi sử dụng boost và tôi là một người mới C ++, vì vậy có lẽ tôi còn thiếu thứ gì đó - nhưng trong mã của riêng tôi, tôi phải chỉ định loại, như vậy: boost :: shared_lock <shared_mutex> lock (_truy cập);
Ken Smith

2
Tôi đang cố gắng tự mình sử dụng cái này nhưng tôi gặp lỗi. thiếu đối số mẫu trước 'khóa'. Có ý kiến ​​gì không?
Matt

2
@shaz Đó là phạm vi, nhưng bạn có thể phát hành sớm bằng .unlock () nếu cần.
mmocny

4
Tôi đã thêm các đối số mẫu bị thiếu.

1
@raaj, bạn có thể nhận được lift_lock, nhưng nâng cấp lên một khóa duy nhất sẽ chặn cho đến khi shared_lock được phát hành
1800 THÔNG TIN

166

1800 INFORMATION ít nhiều đúng, nhưng có một số vấn đề tôi muốn sửa.

boost::shared_mutex _access;
void reader()
{
  boost::shared_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access
}

void conditional_writer()
{
  boost::upgrade_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access

  if (something) {
    boost::upgrade_to_unique_lock< boost::shared_mutex > uniqueLock(lock);
    // do work here, but now you have exclusive access
  }

  // do more work here, without anyone having exclusive access
}

void unconditional_writer()
{
  boost::unique_lock< boost::shared_mutex > lock(_access);
  // do work here, with exclusive access
}

Cũng xin lưu ý, không giống như shared_lock, chỉ một luồng duy nhất có thể có được một lift_lock tại một thời điểm, ngay cả khi nó không được nâng cấp (điều mà tôi nghĩ là rất khó xử khi gặp phải nó). Vì vậy, nếu tất cả độc giả của bạn đều là những người viết có điều kiện, bạn cần tìm giải pháp khác.


1
Chỉ để bình luận về "giải pháp khác". Khi tất cả độc giả của tôi nơi những người viết có điều kiện, điều tôi làm là họ luôn có được một shared_lock và khi tôi cần nâng cấp để viết privilages, tôi sẽ .unlock () trình đọc đó khóa và có được một unique_lock mới. Điều này sẽ làm phức tạp tính logic của ứng dụng của bạn và bây giờ có một cơ hội để những người viết khác thay đổi trạng thái so với khi bạn đọc lần đầu tiên.
mmocny

8
Dòng có nên boost::unique_lock< boost::shared_mutex > lock(lock);đọc boost::unique_lock< boost::shared_mutex > lock( _access ); không?
SteveWilkinson

4
Lời cảnh báo cuối cùng đó rất kỳ lạ. Nếu chỉ có một luồng có thể chứa một lift_lock tại một thời điểm, thì sự khác biệt giữa lift_lock và unique_lock là gì?
Ken Smith

2
@Ken Tôi không rõ lắm, nhưng lợi ích của lift_lock là nó không chặn nếu hiện tại có một số shared_locks được mua lại (ít nhất là không cho đến khi bạn nâng cấp lên duy nhất). Tuy nhiên, luồng thứ hai để thử và có được lift_lock sẽ chặn, ngay cả khi luồng đầu tiên chưa nâng cấp lên thành duy nhất, điều mà tôi không ngờ tới.
mmocny

6
Đây là một vấn đề tăng cường đã biết. Có vẻ như nó đã được giải quyết ở phiên bản beta tăng 1.50: svn.boost.org/trac/boost/ticket/5516
Ofek Shilon

47

Kể từ C ++ 17 (VS2015), bạn có thể sử dụng tiêu chuẩn cho khóa đọc-ghi:

#include <shared_mutex>

typedef std::shared_mutex Lock;
typedef std::unique_lock< Lock > WriteLock;
typedef std::shared_lock< Lock > ReadLock;

Lock myLock;


void ReadFunction()
{
    ReadLock r_lock(myLock);
    //Do reader stuff
}

void WriteFunction()
{
     WriteLock w_lock(myLock);
     //Do writer stuff
}

Đối với phiên bản cũ hơn, bạn có thể sử dụng boost với cú pháp tương tự:

#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock >  WriteLock;
typedef boost::shared_lock< Lock >  ReadLock;

5
Tôi cũng muốn nói typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
cây nho

6
Không cần bao gồm toàn bộ chủ đề. Hpp. Nếu bạn chỉ cần ổ khóa, hãy bao gồm ổ khóa. Nó không phải là một triển khai nội bộ. Giữ các bao gồm ở mức tối thiểu.
Yochai Timmer

5
Chắc chắn là cách triển khai đơn giản nhất nhưng tôi nghĩ thật khó hiểu khi coi cả mutexes và ổ khóa là Locks. Một mutex là một mutex, một khóa là một thứ duy trì nó ở trạng thái bị khóa.
Tim MB

17

Chỉ để thêm một số thông tin kinh nghiệm khác, tôi đã điều tra toàn bộ vấn đề về các ổ khóa có thể nâng cấp và Ví dụ về tăng shared_mutex (nhiều lần đọc / một lần ghi)? là một câu trả lời hay, bổ sung thêm thông tin quan trọng rằng chỉ một luồng có thể có một khóa nâng cấp ngay cả khi nó không được nâng cấp, điều đó quan trọng vì nó có nghĩa là bạn không thể nâng cấp từ khóa chia sẻ lên một khóa duy nhất mà không giải phóng khóa chia sẻ trước. (Điều này đã được thảo luận ở nơi khác nhưng chủ đề thú vị nhất là ở đây http://thread.gmane.org/gmane.comp.lib.boost.devel/214394 )

Tuy nhiên, tôi đã tìm thấy một sự khác biệt quan trọng (không có tài liệu) giữa một luồng đang chờ nâng cấp lên một khóa (tức là cần đợi tất cả người đọc giải phóng khóa chia sẻ) và một khóa người viết đang chờ điều tương tự (tức là một unique_lock).

  1. Chuỗi đang chờ unique_lock trên shared_mutex chặn bất kỳ độc giả mới nào đến, họ phải đợi người viết yêu cầu. Điều này đảm bảo độc giả không bỏ đói nhà văn (tuy nhiên tôi tin rằng nhà văn có thể bỏ đói độc giả).

  2. Chuỗi đang chờ nâng cấp có thể nâng cấp cho phép các chủ đề khác nhận được khóa chia sẻ, do đó, chuỗi này có thể bị bỏ đói nếu người đọc rất thường xuyên.

Đây là một vấn đề quan trọng cần xem xét, và có lẽ nên được ghi lại.


3
Điều Terekhov algorithmđó đảm bảo rằng 1.nhà văn không thể bỏ đói độc giả. Xem này . Nhưng 2.là sự thật. Nâng cấp không đảm bảo tính công bằng. Xem này .
JonasVautherin

2

Sử dụng một semaphore có số lượng bằng số lượng độc giả. Hãy để mỗi người đọc lấy một số lượng semaphore để đọc, theo cách đó tất cả họ có thể đọc cùng một lúc. Sau đó, hãy để người viết lấy TẤT CẢ các số đếm semaphore trước khi viết. Điều này khiến người viết phải đợi tất cả các lần đọc kết thúc và sau đó chặn các lần đọc trong khi viết.


(1) Làm thế nào để bạn làm cho một người viết giảm số lượng một lượng tùy ý về nguyên tử ? (2) Nếu người viết bằng cách nào đó giảm số đếm xuống 0, thì làm thế nào để người đọc đã chạy xong trước khi viết?
Ofek Shilon

Ý tưởng tồi: Nếu hai người viết cố gắng truy cập đồng thời, bạn có thể gặp bế tắc.
Caduchon

2

Phản hồi tuyệt vời của Jim Morris, tôi tình cờ phát hiện ra điều này và tôi phải mất một lúc để hình dung. Dưới đây là một số đoạn mã đơn giản cho thấy rằng sau khi gửi "yêu cầu" tăng unique_lock (phiên bản 1.54) sẽ chặn tất cả các yêu cầu shared_lock. Điều này rất thú vị vì dường như đối với tôi, việc lựa chọn giữa unique_lock và upgradedable_lock cho phép nếu chúng ta muốn ưu tiên ghi hoặc không ưu tiên.

Ngoài ra (1) trong bài đăng của Jim Morris dường như mâu thuẫn với điều này: Boost shared_lock. Đọc ưa thích?

#include <iostream>
#include <boost/thread.hpp>

using namespace std;

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > UniqueLock;
typedef boost::shared_lock< Lock > SharedLock;

Lock tempLock;

void main2() {
    cout << "10" << endl;
    UniqueLock lock2(tempLock); // (2) queue for a unique lock
    cout << "11" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(1));
    lock2.unlock();
}

void main() {
    cout << "1" << endl;
    SharedLock lock1(tempLock); // (1) aquire a shared lock
    cout << "2" << endl;
    boost::thread tempThread(main2);
    cout << "3" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(3));
    cout << "4" << endl;
    SharedLock lock3(tempLock); // (3) try getting antoher shared lock, deadlock here
    cout << "5" << endl;
    lock1.unlock();
    lock3.unlock();
}

Tôi thực sự đang gặp sự cố khi tìm ra lý do tại sao đoạn mã trên gặp sự cố trong khi mã trong [ stackoverflow.com/questions/12082405/… hoạt động.
dale1209

1
Nó thực sự bế tắc ở (2), không phải ở (3), bởi vì (2) đang chờ (1) giải phóng khóa của nó. Hãy nhớ rằng: để có được một khóa duy nhất, bạn cần đợi tất cả các khóa dùng chung hiện có hoàn tất.
JonasVautherin

@JonesV, ngay cả khi (2) đợi tất cả các khóa chia sẻ kết thúc, nó sẽ không phải là một bế tắc vì đó là một chuỗi khác với chuỗi đã có (1), nếu dòng (3) không tồn tại, chương trình sẽ kết thúc mà không có bế tắc.
SagiLow
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.