【问题标题】:Looking for critique of my reader/writer implementation [closed]寻找对我的读者/作家实施的批评[关闭]
【发布时间】:2014-02-03 21:31:00
【问题描述】:

我在 c++11 中实现了读写器问题……我想知道它有什么问题,因为这些事情我自己很难预测。

  • 共享数据库:
    • 没有作者时读者可以访问数据库
    • 当没有读者或作者时,作者可以访问数据库
    • 一次只有一个线程处理状态变量

示例有 3 个 reader 和 1 个 writer,但也使用 2 个或更多 writer....

代码:

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

【问题讨论】:

  • “给我反馈代码。”....听起来你应该把它放在 codereview.stackexchange.com
  • 这个问题似乎是题外话,因为它属于 codereview.stackexchange.com
  • 为什么我不能在这里问它,因为它是一个设计问题?
  • 这个问题很重要,因为它还询问如何与 C++11 互斥 API 进行互操作。你只需要更深入地阅读才能意识到这一点。这里唯一犯下的“罪行”是问题中开头句的不幸措辞,引发了题外话。我已投票决定重新开放并敦促其他人也这样做。归根结底,SO 应该很有用。把这个问题追到姐妹网站也没有用。我的回复包含的信息将对 SO 上的大量 C++ 编码人员有所帮助。
  • @HowardHinnant 抱歉,我在这里没有看到任何其他问题,但“请给我一些反馈”。如果您认为可以将其编辑为适当的问题,请这样做。我没有看到这个问题,因此即使重新开放,也会有另外 5 名投票者接近并关闭它。就目前而言,收盘似乎完全合理。

标签: c++ multithreading algorithm c++11 locking


【解决方案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()

4coutdata_base_thread_write 中的m.lock() 包装实际上是不必要的,因为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 通过将 AW 减为 0 来释放写锁,并发出信号 okToWrite

在第 5 步中,线程 2 尽管没有收到信号,但被 虚假地唤醒,注意到 AW == 0,并通过将 WR 设置为 0 和 AR 设置为 1 来获取读锁.

在步骤 6 中,线程 3 接收到信号,注意到 AW == 0,并通过将 WW 设置为 0 并将 AW 设置为 1 来获取写锁。

在步骤 6 中,线程 2 拥有读锁,线程 3 拥有写锁(同时)。

6ReadersWriters 类有两个功能:

  1. 它实现了一个读/写互斥锁。
  2. 它为线程执行任务。

更好的设计将利用 C++11 中建立的互斥锁/锁框架:

用成员创建一个ReaderWriter 互斥锁:

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

前两个名称 lockunlock 故意与 C++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_sharedunlock_shared 名称,是因为 C++1y(我们希望 y 是 4)工作草案中当前的 std::shared_lock&lt;T&gt; 类型。它记录在N3659 中。 然后你可以这样说:

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

即只需创建一个独立的 ReaderWriter 互斥类型,并为成员函数精心选择名称,您就可以获得与标准定义的锁、condition_variable_any 和锁定算法的互操作性。

请参阅N2406,了解此框架的更深入原理。

【讨论】:

  • 嗨,霍华德,感谢您的反馈,我尝试自己实现锁定/解锁以理解这个概念。你是如何在 5 中创建表格的?我修改了我的代码...我不知道如何在此处重新粘贴代码...所以我将我的代码上传到:link ...它是否解决了您看到的问题?
  • 我只是通过查看源代码生成了表格。我无法通过实验证明这种状态。是的,我相信您的修改可以解决问题。我现在认为您已经非常接近重新发明 Alexander Terekhov 的算法 8a,这是一项令人印象深刻的壮举。 :-)
  • 霍华德:无需重新发明 :) 与使用互斥锁直接相比,shared_lock 是否有一些开销:mutex.lock() ... mutex.unlock() ?
  • @Gerald: shared_lock 包含一个可能为空的指向互斥锁的指针,以及一个布尔值,指示shared_lock 是否拥有互斥锁上的锁。有一个不变量,如果 bool 为真,则指针非空(指向有效且共享锁定的互斥体)。唯一的开销是~shared_lock() 在调用unlock_shared() 之前将首先检查布尔值是否为真。恕我直言,这种开销是无关紧要的。这与unique_lock 的开销完全相同。
猜你喜欢
  • 2011-06-13
  • 1970-01-01
  • 2014-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多