В поисках критики моей реализации читатель / писатель

0

я реализовал проблему с читателями/писателями в С++ 11... Я хотел бы знать, что с ним не так, потому что подобные вещи трудно предсказать самостоятельно.

  • Общая база данных:
    • Читатели могут получить доступ к базе данных, когда нет писателей
    • Писатели могут получить доступ к базе данных, когда нет читателей или писателей
    • Только один поток манипулирует переменными состояния за раз

в примере есть 3 читателя и 1 писатель, но также используют 2 или более автора....

Код:

class ReadersWriters {
private:
    int AR; // number of active readers
    int WR; // number of waiting readers
    int AW; // number of active writers
    int WW; // number of waiting writers
    mutex lock;
    mutex m;
    condition_variable okToRead;
    condition_variable okToWrite;

    int data_base_variable;

public:
    ReadersWriters() : AR(0), WR(0), AW(0), WW(0), data_base_variable(0) {}

    void read_lock() {
        unique_lock<mutex> l(lock);

        WR++; // no writers exist
        // is it safe to read?
        okToRead.wait(l, [this](){ return WW == 0; });
        okToRead.wait(l, [this](){ return AW == 0; });
        WR--; // no longer waiting

        AR++;  // now we are active
    }

    void read_unlock() {
        unique_lock<mutex> l(lock);

        AR--; // no longer active

        if (AR == 0 && WW > 0) { // no other active readers
            okToWrite.notify_one(); // wake up one writer
        }
    }

    void write_lock() {
        unique_lock<mutex> l(lock);

        WW++; // no active user exist
        // is it safe to write?
        okToWrite.wait(l, [this](){ return AR == 0; });
        okToWrite.wait(l, [this](){ return AW == 0; });
        WW--; // no longer waiting

        AW++; // no we are active
    }
    void write_unlock() {
        unique_lock<mutex> l(lock);

        AW--; // no longer active

        if (WW > 0) { // give priority to writers
            okToWrite.notify_one(); // wake up one writer
        }
        else if (WR > 0) { // otherwize, wake reader
            okToRead.notify_all(); // wake all readers
        }
    }

    void data_base_thread_write(unsigned int thread_id) {
        for (int i = 0; i < 10; i++) {
            write_lock();

            data_base_variable++;
            m.lock();
            cout << "data_base_thread: " << thread_id << "...write: " << data_base_variable << endl;
            m.unlock();
            write_unlock();

            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    void data_base_thread_read(unsigned int thread_id) {
        for (int i = 0; i < 10; i++) {
            read_lock();

            m.lock();
            cout << "data_base_thread: " << thread_id << "...read: " << data_base_variable << endl;
            m.unlock();

            read_unlock();

            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
};

int main() {
    // your code goes here
    ReadersWriters rw;

    thread w1(&ReadersWriters::data_base_thread_write, &rw, 0);
    thread r1(&ReadersWriters::data_base_thread_read, &rw, 1);
    thread r2(&ReadersWriters::data_base_thread_read, &rw, 2);
    thread r3(&ReadersWriters::data_base_thread_read, &rw, 3);

    w1.join();
    r1.join();
    r2.join();
    r3.join();

    cout << "\nThreads successfully completed..." << endl;

    return 0;
}
  • 5
    "Дайте мне отзыв о коде." .... звучит так, как будто вы должны были разместить это на codereview.stackexchange.com
  • 0
    Этот вопрос кажется не по теме, потому что он принадлежит codereview.stackexchange.com
Показать ещё 5 комментариев
Теги:
multithreading
c++11
algorithm
locking

1 ответ

1

Обратная связь:

1. В нем отсутствуют все необходимые #includes.

2. Он предполагает using namespace std, что является плохим стилем в объявлениях, поскольку это загрязняет всех ваших клиентов с пространством имен std.

3. Выпуск ваших замков не является безопасным:

write_lock();

data_base_variable++;
m.lock();
cout << "data_base_thread: " << thread_id << "...write: " << data_base_variable << endl;
m.unlock();           // leaked if an exception is thrown after m.lock()
write_unlock();       // leaked if an exception is thrown after write_lock()

4. m.lock() cout в data_base_thread_write действительно не нужна, поскольку write_lock() должен уже предоставлять эксклюзивный доступ. Однако я понимаю, что это всего лишь демоверсия.

5. Я думаю, что я вижу ошибку в логике чтения/записи:

step   1     2     3    4     5    6
WR     0     1     1    1     0    0
AR     0     0     0    0     1    1
WW     0     0     1    1     1    0
AW     1     1     1    0     0    1

На шаге 1 поток 1 имеет блокировку записи.

На шаге 2 поток 2 пытается получить блокировку чтения, увеличивает WR и блокирует второй okToRead, ожидая AW == 0.

На шаге 3 поток 3 пытается получить блокировку записи, увеличивает WW и блокирует второй okToWrite, ожидая AW == 0.

На шаге 4 поток 1 освобождает, блокирует запись, okToWrite AW до 0 и сигнализирует okToWrite.

На шаге 5 поток 2, несмотря на то, что он не сигнализирован, пробуждается ложно, отмечает, что AW == 0, и захватывает блокировку чтения, устанавливая WR в 0 и AR на 1.

На шаге 6 поток 3 принимает сигнал, отмечает, что AW == 0, и захватывает блокировку записи, устанавливая WW в 0 и AW на 1.

На этапе 6 оба потока 2 владеют блокировкой чтения, а поток 3 владеет блокировкой записи (одновременно).

6. Класс ReadersWriters имеет две функции:

  1. Он реализует мьютекс чтения/записи.
  2. Он реализует задачи для выполнения потоков.

Лучшая конструкция будет использовать фреймы mutex/lock, установленные в С++ 11:

Создайте мьютекс ReaderWriter с членами:

// unique ownership
void lock();      // write_lock
void unlock();    // write_unlock
// shared ownership
lock_shared();    // read_lock
unlock_shared();  // read_unlock

Первые два имени, lock и unlock являются целенаправленно теми же именами, что и те, которые используются в Mutex С++ 11. Просто это позволяет вам делать такие вещи, как:

std::lock_guard<ReaderWriter>  lk1(mut);
// ...
std::unique_lock<ReaderWriter> lk2(mut);
// ...
std::condition_variable_any cv;
cv.wait(lk2);  // wait using the write lock

И если вы добавите:

void try_lock();

Затем вы также можете:

std::lock(lk2, <any other std or non-std locks>);  // lock multiple locks

lock_shared и unlock_shared выбраны из-за типа std::shared_lock<T> который в настоящее время находится в С++ 1y (мы надеемся, что y is 4) работает над проектом. Он задокументирован в N3659. И тогда вы можете сказать такие вещи, как:

std::shared_lock<ReaderWriter> lk3(mut);   // read_lock
std::condition_variable_any cv;
cv.wait(lk3);  // wait using the read lock

Т.е. просто создавая автономный ReaderWriter мьютекса ReaderWriter с очень тщательно подобранными именами для функций-членов, вы получаете возможность взаимодействия с std-определенными блокировками, condition_variable_any и алгоритмами блокировки.

См. N2406 для более глубокого обоснования этой структуры.

  • 0
    Привет, Говард, спасибо за ваш отзыв. Я попытался самостоятельно выполнить блокировку / разблокировку, чтобы понять концепцию. как вы создали таблицу в 5? я немного изменил свой код ... я не знаю, как перекомпилировать код здесь ... поэтому я загрузил свой код в: ссылку ... это решает проблему, которую вы видели?
  • 0
    Я сгенерировал таблицу только путем проверки исходного кода. Я не смог продемонстрировать это состояние экспериментом. Да, я считаю, что ваша модификация решает проблему. Теперь я думаю, что вы невероятно близки к тому, чтобы заново изобрести алгоритм Александра Терехова 8а, что является довольно впечатляющим подвигом. :-)
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню