4

我已经使用 C++14 的 shared_timed_mutex 编写了读写器问题的实现。在我看来,下面的代码应该会导致写入器饿死,因为太多的读取器线程一直在数据库上工作(在这个例子中是一个简单的数组):写入器没有机会获得锁。

mutex cout_mtx;                 // controls access to standard output
shared_timed_mutex db_mtx;      // controls access to data_base

int data_base[] = { 0, 0, 0, 0, 0, 0 };

const static int NR_THREADS_READ = 10; 
const static int NR_THREADS_WRITE = 1;
const static int SLEEP_MIN = 10;
const static int SLEEP_MAX = 20;

void read_database(int thread_nr) {
    shared_lock<shared_timed_mutex> lck(db_mtx, defer_lock); // create a lock based on db_mtx but don't try to acquire the mutex yet
    while (true) {
        // generate new random numbers
        std::random_device r;
        std::default_random_engine e(r());
        std::uniform_int_distribution<int> uniform_dist(SLEEP_MIN, SLEEP_MAX);
        std::uniform_int_distribution<int> uniform_dist2(0, 5);
        int sleep_duration = uniform_dist(e);   // time to sleep between read requests 
        int read_duration = uniform_dist(e);    // duration of reading from data_base
        int cell_number = uniform_dist2(e);     // what data cell will be read from
        int cell_value = 0;

        // wait some time before requesting another access to the database
        this_thread::sleep_for(std::chrono::milliseconds(sleep_duration));

        if (!lck.try_lock()) {

            lck.lock(); // try to get the lock in blocked state
        }

        // read data
        cell_value = data_base[cell_number];

        lck.unlock();

    }
}

void write_database(int thread_nr) {
    unique_lock<shared_timed_mutex> lck(db_mtx, defer_lock); // create a lock based on db_mtx but don't try to acquire the mutex yet
    while (true) {
        // generate new random numbers
        std::random_device r;
        std::default_random_engine e(r());
        std::uniform_int_distribution<int> uniform_dist(SLEEP_MIN, SLEEP_MAX);
        std::uniform_int_distribution<int> uniform_dist2(0, 5);
        int sleep_duration = uniform_dist(e);   // time to sleep between write requests 
        int read_duration = uniform_dist(e);    // duration of writing to data_base
        int cell_number = uniform_dist2(e);     // what data cell will be written to

        // wait some time before requesting another access to the database
        this_thread::sleep_for(std::chrono::milliseconds(sleep_duration));

        // try to get exclusive access
        cout_mtx.lock();
        cout << "Writer <" << thread_nr << "> requesting write access." << endl;
        cout_mtx.unlock();

        if (!lck.try_lock()) {

            lck.lock(); // try to get the lock in blocked state
        }

        // write data
        data_base[cell_number] += 1;

        lck.unlock();

    }
}

当线程正在读取、写入、尝试以阻塞模式或通过try_lock()方法获取锁时,我向标准输出添加了一些输出,但为了清楚起见,我删除了输出。我在 main 方法中进一步启动线程。当我运行程序时,编写器总是有机会写入数组(导致所有读取器线程阻塞,这没关系),但正如我上面所说,编写器根本不应该能够访问,因为有太多许多读取器线程从数组中读取。即使我根本不让读取线程进入睡眠状态(参数 0),写入线程也会以某种方式找到获取互斥锁的方法。那我怎么让作家饿死呢?

4

1 回答 1

6

一个高质量的实现std::shared_timed_mutex不会让读者和作者饿死。然而,随着读者数量/作者数量的增加,作者获得锁的概率越小。使用您当前的设置(1 位作者对 10 位读者),我猜作者大约有 9% 的时间会获得锁定。当您增加该比率时,编写器将获得更少的锁定,但永远不会 100% 被饿死。

如果你只让作者在a下获得try_lock,那么你100%饿死它的机会会大大增加。

std::shared_timed_mutex允许在不饿死读者或作者的情况下实现的算法的存在是std::shared_timed_mutex没有允许您决定读者优先级或作者优先级的 API 的原因。

算法

想象一下,互斥体中有两个门: gate1gate2

无论您是读者还是作家,要克服gate1它(几乎)都无关紧要。如果 里面有其他作者gate1,你就进不去。读者必须遵循一个在实践中永远不会发挥作用的附加规则:如果已经有最大数量的读者过去gate1,你就不能进去。

一旦过去gate1,读者就拥有共享锁。

一旦过去gate1,作家就不再拥有唯一锁。他必须在外面进一步等待,gate2直到没有更多的读者持有共享锁。一旦过去gate2,作者拥有唯一的锁。

这个算法是“公平的”,因为如果你是读者或作家,通过它几乎没有什么区别gate1。如果在 之外有一堆读写器gate1,下一个进入的线程是由操作系统决定的,而不是由这个算法决定的。所以你可以把它想象成掷骰子。如果你的读者数量与作者竞争的数量相同gate1,那么下一个通过的读者或作者的几率是 50/50 gate1

于 2016-06-28T00:19:03.367 回答