Приклад boost shared_mutex (кілька читання / одна запис)?


116

У мене є багатопотокова програма, якій доводиться часто читати деякі дані, а періодично ці дані оновлюються. Зараз mutex зберігає доступ до цих даних безпечним, але це дорого, тому що я хотів би, щоб декілька потоків могли читати одночасно, і блокувати їх лише тоді, коли потрібно оновлення (оновлення потоку може чекати, коли інші потоки завершаться) .

Я думаю, що boost::shared_mutexце має робити, але мені не ясно, як це використовувати, і я не знайшов чіткого прикладу.

Хтось має простий приклад, який я міг би використовувати для початку?


Приклад ІНФОРМАЦІЇ 1800 правильний. Дивіться також цю статтю: Що нового у Boost Threads .
Ассаф Лав'є

можливий дублікат
записів

Відповіді:


102

Схоже, ви зробили б щось подібне:

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
Я вперше використовую boost, і я новачок на C ++, тому, можливо, чогось мені не вистачає - але у власному коді я повинен був вказати тип, наприклад: boost :: shared_lock <shared_mutex> замок (_доступ);
Кен Сміт

2
Я намагаюся використовувати це сам, але я отримую помилку. відсутні аргументи шаблону перед "блокуванням". Будь-які ідеї?
Метт

2
@shaz Ці області охоплюються, але ви можете звільнити їх достроково за допомогою .unlock (), якщо вам потрібно.
mmocny

4
Я додав відсутні аргументи шаблону.

1
@raaj ви можете отримати upgrade_lock, але оновлення до унікального блокування заблокується, поки не буде випущений спільний_блок
1800 ІНФОРМАЦІЯ

166

ІНФОРМАЦІЯ 1800 є більш-менш правильною, але є кілька питань, які я хотів виправити.

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
}

Також зауважте, на відміну від shared_lock, лише один потік може придбати оновлення_блок одночасно, навіть коли він не оновлений (що, на мою думку, було незручно, коли я натрапив на нього). Отже, якщо всі ваші читачі - це умовні письменники, вам потрібно знайти інше рішення.


1
Просто прокоментувати "інше рішення". Коли всі мої читачі, де умовні автори, що я робив, були у них, завжди отримують спільний_блок, і коли мені потрібно було оновити, щоб написати привілеї, я б .unlock () заблокував читач і придбав новий унікальний_блок. Це ускладнить логіку вашої програми, і тепер є вікно можливостей для інших авторів змінити стан з моменту першого читання.
mmocny

8
Чи не повинен рядок boost::unique_lock< boost::shared_mutex > lock(lock);читати boost::unique_lock< boost::shared_mutex > lock( _access ); ?
SteveWilkinson

4
Цей останній застереження дуже дивний. Якщо лише один потік може містити оновлення_блок одночасно, яка різниця між оновленням_блок та унікальним_блоком?
Кен Сміт

2
@Ken Мені було не дуже зрозуміло, але користь upgrade_lock полягає в тому, що він не блокується, якщо в даний час придбані деякі спільні_блоки (принаймні, не поки ви не оновите до унікальних). Однак другий потік, який слід спробувати придбати upgrade_lock, заблокує, навіть якщо перший не перейшов до унікального, чого я не очікував.
mmocny

6
Це відоме питання підвищення. Здається, це вирішиться при підвищенні 1,50 бета: svn.boost.org/trac/boost/ticket/5516
Ofek Shilon

47

Оскільки C ++ 17 (VS2015) ви можете використовувати стандарт для блокування читання-запису:

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

Для старшої версії можна використовувати boost з тим же синтаксисом:

#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
Я б також сказав typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
лози

6
Не потрібно включати цілу нитку.hpp. Якщо вам просто потрібні замки, включіть замки. Це не внутрішня реалізація. Зберігайте включення до мінімуму.
Йочай Тіммер

5
Безумовно, найпростіша реалізація, але я думаю, що це заплутано позначати як мутекси та блоки як Locks. Мутекс - це мютекс, замок - це те, що підтримує його в заблокованому стані.
Тім МБ

17

Для того, щоб додати ще емпіричну інформацію, я досліджував всю проблему блоків, що можна оновити , та приклад для підвищення shared_mutex (кілька читань / один запис)? є гарною відповіддю, додаючи важливу інформацію про те, що лише один потік може мати оновлення_блоку, навіть якщо він не оновлений, що важливо, оскільки це означає, що ви не можете оновити зі спільного блокування до унікального блокування, не звільняючи спочатку загальний замок. (Це обговорювалося в іншому місці, але найцікавіша тема тут http://thread.gmane.org/gmane.comp.lib.boost.devel/214394 )

Однак я виявив важливу (незадокументовану) різницю між потоком, який чекає оновлення до блокування (тобто потрібно чекати, коли всі читачі вийдуть із загального блокування), і записом запису, який очікує тієї самої речі (тобто унікальний_блок).

  1. Нитка, яка чекає унікального_блока на shared_mutex, блокує будь-які нові читачі, що надходять, їм доведеться чекати запиту авторів. Це гарантує, що читачі не голодують письменниками (однак, я вважаю, що письменники могли голодувати читачами).

  2. Потік, який чекає на оновлення_lockable, дозволяє іншим потокам отримати загальний замок, тому ця нитка може бути голодованою, якщо читачі дуже часті.

Це важливе питання, яке слід врахувати, і, ймовірно, має бути задокументовано.


3
У Terekhov algorithmгарантує , що 1.письменник не може голодувати читачів. Дивіться це . Але 2.це правда. Оновлення_блок не гарантує справедливості. Дивіться це .
JonasVautherin

2

Скористайтеся семафором з кількістю, що дорівнює кількості читачів. Нехай кожен читач візьме один підрахунок семафору, щоб прочитати, щоб вони могли читати одночасно. Тоді дозвольте письменникові взяти ВСІ підрахунки семафору до написання. Це змушує письменника чекати, коли всі читання закінчать, а потім блокує читання під час написання.


(1) Як зробити так, щоб письменник зменшив кількість довільної кількості атомно ? (2) Якщо письменник якось зменшує кількість до нуля, як він чекає, коли вже запущені читачі закінчать, перш ніж писати?
Офек Шилон

Погана ідея: якщо два автори намагаються отримати доступ одночасно, ви можете мати тупик.
Кадушон

2

Чудова реакція Джима Морріса, я натрапив на це, і мені знадобилося певний час. Ось простий код, який показує, що після надсилання "запиту" на унікальне збільшення_блокування (версія 1.54) блокує всі запити на спільний_блок. Це дуже цікаво, оскільки мені здається, що вибір між унікальним_блоком та оновленням_блоку дозволяє, якщо ми хочемо написати пріоритет або немає пріоритету.

Також (1) у публікації Джима Морріса, здається, суперечить цьому: Підвищити спільний_блок. Читати краще?

#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();
}

У мене насправді виникають проблеми з тим, щоб зрозуміти, чому вищезгадані кодові тупики, поки працює код [ stackoverflow.com/questions/12082405/… .
dale1209

1
Це фактично тупики в (2), а не в (3), тому що (2) чекає, коли (1) звільнить замок. Пам’ятайте: щоб отримати унікальний замок, потрібно дочекатися завершення всіх існуючих загальних блокувань.
JonasVautherin

@JonesV, навіть якщо (2) чекає, коли всі спільні блокування завершаться, це не буде тупиком, оскільки це інший потік, ніж той, який придбав (1), якби рядок (3) не існував, програма закінчити без тупиків.
SagiLow
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.