如何让作家线程挨饿

时间:2016-06-27 23:49:18

标签: c++ multithreading c++14 starvation

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

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),编写器线程也会找到一种获取互斥锁的方法。我怎么让作家饿死呢?

1 个答案:

答案 0 :(得分:6)

std::shared_timed_mutex的高质量实施不会让读者和作家都挨饿。然而,随着读者数量/作者数量的增加,作者获得锁定的可能性越小。根据您当前的设置(1位作者到10位读者),我猜测作者在9%的时间内获得了锁定。随着你增加这个比例,作家将减少锁定,但永远不会100%饿死。

如果你只让作家获得try_lock,那么你100%挨饿的机会将大大增加。

允许std::shared_timed_mutex在没有饥饿的读者和编写者的情况下实现的算法的存在是std::shared_timed_mutex没有允许您指定读者优先级或编写者优先级的API的原因。

算法

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

要过去gate1它(几乎)无论你是读者还是作家都无关紧要。如果gate1内有另一位作家,则无法进入。读者必须遵循一条实际上永远不会发挥作用的额外规则:如果已经有最大数量的读者超过{{1你无法进​​入。

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

一旦超过gate1,作家就拥有唯一锁。他必须在gate1之外等待,直到没有更多读者持有共享锁。一旦超过gate2,作者就拥有了独特的锁。

这个算法是公平的&#34;因为如果你是一个读者或作家,过了gate2就没什么区别了。如果有一堆读者和作者在gate1之外,那么下一个要进入的线程是由OS决定的,而不是由这个算法决定的。所以你可以把它想象成骰子。如果您与竞争gate1的作家拥有相同数量的读者,那么读者或作者是下一个过去gate1的人是50/50的机会。