Contoh untuk meningkatkan shared_mutex (banyak baca / satu tulisan)?

116

Saya memiliki aplikasi multithread yang harus sering membaca beberapa data, dan terkadang data tersebut diperbarui. Saat ini mutex menjaga akses ke data itu tetap aman, tetapi itu mahal karena saya ingin beberapa utas dapat membaca secara bersamaan, dan hanya menguncinya ketika pembaruan diperlukan (utas pembaruan dapat menunggu utas lain selesai) .

Saya pikir inilah yang boost::shared_mutexseharusnya dilakukan, tetapi saya tidak jelas tentang cara menggunakannya, dan belum menemukan contoh yang jelas.

Adakah yang punya contoh sederhana yang bisa saya gunakan untuk memulai?

kevin42
sumber
1800 Contoh INFORMASI benar. Lihat juga artikel ini: Apa yang baru di Boost Threads .
Assaf Lavie
kemungkinan duplikat dari Reader / Writer Locks di C ++
cHao

Jawaban:

102

Sepertinya Anda akan melakukan sesuatu seperti ini:

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
}
1800 INFORMASI
sumber
7
Ini adalah pertama kalinya saya menggunakan boost, dan saya adalah pemula C ++, jadi mungkin ada sesuatu yang saya lewatkan - tetapi dalam kode saya sendiri, saya harus menentukan jenisnya, seperti ini: boost :: shared_lock <shared_mutex> lock (_mengakses);
Ken Smith
2
Saya mencoba menggunakan ini sendiri tetapi saya mendapatkan kesalahan. argumen template yang hilang sebelum 'lock'. Ada ide?
Matt
2
@shaz Mereka memiliki cakupan, tetapi Anda dapat merilisnya lebih awal dengan .unlock () jika perlu.
mmocny
4
Saya telah menambahkan argumen template yang hilang.
1
@raaj Anda bisa mendapatkan upgrade_lock, tetapi mengupgrade ke kunci unik akan memblokir sampai shared_lock dilepaskan
1800 INFORMATION
166

1800 INFORMASI kurang lebih benar, tetapi ada beberapa masalah yang ingin saya perbaiki.

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
}

Juga Perhatikan, tidak seperti shared_lock, hanya satu utas yang dapat memperoleh upgrade_lock pada satu waktu, bahkan ketika itu tidak ditingkatkan (yang menurut saya canggung ketika saya menemukannya). Jadi, jika semua pembaca Anda adalah penulis bersyarat, Anda perlu mencari solusi lain.

mmocny.dll
sumber
1
Sekadar mengomentari "solusi lain". Ketika semua pembaca saya di mana penulis bersyarat, apa yang saya lakukan adalah membuat mereka selalu mendapatkan shared_lock, dan ketika saya perlu memutakhirkan untuk menulis hak istimewa, saya akan .unlock () kunci pembaca dan mendapatkan unique_lock baru. Ini akan mempersulit logika aplikasi Anda, dan sekarang ada jendela kesempatan bagi penulis lain untuk mengubah status dari saat Anda pertama kali membaca.
mmocny
8
Bukankah seharusnya baris boost::unique_lock< boost::shared_mutex > lock(lock);membaca boost::unique_lock< boost::shared_mutex > lock( _access ); ?
SteveWilkinson
4
Peringatan terakhir itu sangat aneh. Jika hanya satu utas yang dapat menahan upgrade_lock dalam satu waktu, apa perbedaan antara upgrade_lock dan unique_lock?
Ken Smith
2
@Ken Saya tidak begitu jelas, tetapi manfaat upgrade_lock adalah tidak memblokir jika saat ini ada beberapa shared_lock yang diperoleh (setidaknya sampai Anda meningkatkan ke unique). Namun, utas kedua untuk mencoba dan memperoleh pemutakhiran_lock akan diblokir, meskipun utas pertama belum ditingkatkan menjadi unik, yang tidak saya harapkan.
mmocny
6
Ini adalah masalah peningkatan yang diketahui. Tampaknya diselesaikan di boost 1,50 beta: svn.boost.org/trac/boost/ticket/5516
Ofek Shilon
47

Sejak C ++ 17 (VS2015) Anda dapat menggunakan standar untuk kunci baca-tulis:

#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
}

Untuk versi yang lebih lama, Anda dapat menggunakan boost dengan sintaks yang sama:

#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;
Yochai Timmer
sumber
5
Saya juga akan mengatakan typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
tanaman merambat
6
Tidak perlu menyertakan seluruh thread.hpp. Jika Anda hanya membutuhkan kuncinya, sertakan kuncinya. Ini bukan implementasi internal. Jaga agar memasukkan seminimal mungkin.
Yochai Timmer
5
Jelas implementasi yang paling sederhana tetapi saya pikir itu membingungkan untuk merujuk pada mutex dan kunci sebagai Locks. Mutex adalah mutex, kunci adalah sesuatu yang menjaganya dalam keadaan terkunci.
Tim MB
17

Hanya untuk menambahkan beberapa info empiris, saya telah menyelidiki seluruh masalah kunci yang dapat diupgrade, dan Contoh untuk meningkatkan shared_mutex (banyak baca / satu tulisan)? adalah jawaban yang bagus menambahkan info penting bahwa hanya satu utas yang dapat memiliki upgrade_lock meskipun tidak ditingkatkan, itu penting karena itu berarti Anda tidak dapat meningkatkan dari kunci bersama menjadi kunci unik tanpa melepaskan kunci bersama terlebih dahulu. (Ini telah dibahas di tempat lain tetapi utas yang paling menarik ada di sini http://thread.gmane.org/gmane.comp.lib.boost.devel/214394 )

Namun saya menemukan perbedaan penting (tidak berdokumen) antara utas menunggu peningkatan ke kunci (yaitu perlu menunggu semua pembaca untuk melepaskan kunci bersama) dan kunci penulis menunggu hal yang sama (yaitu unique_lock).

  1. Utas yang menunggu unique_lock di shared_mutex memblokir pembaca baru yang masuk, mereka harus menunggu permintaan penulis. Hal ini memastikan pembaca tidak membuat penulis kelaparan (namun saya yakin penulis dapat membuat pembaca kelaparan).

  2. Utas yang menunggu upgradeable_lock untuk meningkatkan memungkinkan utas lain mendapatkan kunci bersama, jadi utas ini bisa kelaparan jika pembaca sangat sering.

Ini adalah masalah penting untuk dipertimbangkan, dan mungkin harus didokumentasikan.

Jim Morris
sumber
3
Yang Terekhov algorithmmemastikan 1., penulis tidak bisa membuat pembacanya kelaparan. Lihat ini . Tapi 2.memang benar. Upgrade_lock tidak menjamin keadilan. Lihat ini .
JonasVautherin
2

Gunakan semaphore dengan hitungan yang sama dengan jumlah pembaca. Biarkan setiap pembaca mengambil satu hitungan semafor untuk membaca, dengan cara itu mereka semua dapat membaca pada waktu yang sama. Kemudian biarkan penulis mengambil SEMUA hitungan semaphore sebelum menulis. Hal ini menyebabkan penulis menunggu semua pembacaan selesai dan kemudian memblokir pembacaan saat menulis.

R Virzi
sumber
(1) Bagaimana Anda membuat seorang penulis mengurangi hitungan dengan jumlah yang berubah-ubah secara atom ? (2) Jika penulis entah bagaimana mengurangi hitungan menjadi nol, bagaimana cara menunggu pembaca yang sudah berjalan selesai sebelum menulis?
Ofek Shilon
Ide buruk: Jika dua penulis mencoba mengakses secara bersamaan, Anda bisa menemui jalan buntu.
Caduchon
2

Tanggapan yang luar biasa oleh Jim Morris, saya menemukan ini dan butuh beberapa saat untuk memikirkannya. Berikut adalah beberapa kode sederhana yang menunjukkan bahwa setelah mengirimkan "request" untuk unique_lock boost (versi 1.54) memblokir semua permintaan shared_lock. Ini sangat menarik karena menurut saya memilih antara unique_lock dan upgradeable_lock memungkinkan jika kita ingin menulis prioritas atau tanpa prioritas.

Juga (1) dalam posting Jim Morris tampaknya bertentangan dengan ini: Boost shared_lock. Baca lebih disukai?

#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();
}
dale1209
sumber
Saya sebenarnya mengalami kesulitan untuk mencari tahu mengapa kode di atas mengalami kebuntuan sementara kode di [ stackoverflow.com/questions/12082405/… berfungsi.
dale1209
1
Ini sebenarnya menemui jalan buntu di (2), bukan di (3), karena (2) menunggu (1) untuk melepaskan kuncinya. Ingat: untuk mendapatkan kunci unik, Anda harus menunggu hingga semua kunci bersama yang ada selesai.
JonasVautherin
@JonesV, bahkan jika (2) menunggu semua kunci bersama selesai, itu tidak akan menjadi kebuntuan karena ini adalah utas yang berbeda dari yang diperoleh (1), jika baris (3) tidak ada, program akan selesai tanpa kebuntuan.
SagiLow