我使用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),编写器线程也会找到一种获取互斥锁的方法。我怎么让作家饿死呢?
答案 0 :(得分:6)
std::shared_timed_mutex
的高质量实施不会让读者和作家都挨饿。然而,随着读者数量/作者数量的增加,作者获得锁定的可能性越小。根据您当前的设置(1位作者到10位读者),我猜测作者在9%的时间内获得了锁定。随着你增加这个比例,作家将减少锁定,但永远不会100%饿死。
如果你只让作家获得try_lock
,那么你100%挨饿的机会将大大增加。
允许std::shared_timed_mutex
在没有饥饿的读者和编写者的情况下实现的算法的存在是std::shared_timed_mutex
没有允许您指定读者优先级或编写者优先级的API的原因。
算法
想象一下,互斥锁中有两个门:gate1
和gate2
。
要过去gate1
它(几乎)无论你是读者还是作家都无关紧要。如果gate1
内有另一位作家,则无法进入。读者必须遵循一条实际上永远不会发挥作用的额外规则:如果已经有最大数量的读者超过{{1你无法进入。
一旦超过gate1
,读者就拥有共享锁。
一旦超过gate1
,作家就不拥有唯一锁。他必须在gate1
之外等待,直到没有更多读者持有共享锁。一旦超过gate2
,作者就拥有了独特的锁。
这个算法是公平的&#34;因为如果你是一个读者或作家,过了gate2
就没什么区别了。如果有一堆读者和作者在gate1
之外,那么下一个要进入的线程是由OS决定的,而不是由这个算法决定的。所以你可以把它想象成骰子。如果您与竞争gate1
的作家拥有相同数量的读者,那么读者或作者是下一个过去gate1
的人是50/50的机会。