我已经使用 C++14 的 shared_timed_mutex 编写了读写器问题的实现。在我看来,以下代码应该会导致 Writer 饿死,因为有太多的读取线程一直在处理数据库(在这个例子中是一个简单的数组):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),编写者线程也会以某种方式找到获取互斥量的方法。那我怎么让作者饿死呢?
最佳答案
std::shared_timed_mutex
的高质量实现不会让读者或作者饿死。然而,随着读者数量/写者数量的增长,写者获得锁的可能性越小。根据您当前的设置(1 位作者对 10 位读者),我猜测作者大约有 9% 的时间获得了锁定。当您增加该比率时,作者将获得更少的锁,但永远不会 100% 饿死。
如果你只让写入器在try_lock
下获取,那么你100%饿死它的几率会大大增加。
允许 std::shared_timed_mutex
实现而不会让读者或作者挨饿的算法的存在是 std::shared_timed_mutex
没有 API 的原因允许您指定读者优先级或作者优先级。
算法
假设互斥体中有两个门:gate1
和gate2
。
要通过 gate1
,(几乎)无论您是读者还是作者都无关紧要。如果 gate1
中有另一个 writer,你就进不去。读者必须遵循一个额外的规则,这个规则在实践中永远不会发挥作用:如果已经有最大数量的读者超过了 >gate1
,你进不去。
一旦通过gate1
,读者就拥有共享锁。
一旦通过 gate1
,写入者不拥有唯一锁。他必须在 gate2
外进一步等待,直到不再有读者持有共享锁。一旦通过 gate2
,作者就拥有唯一锁。
此算法是“公平的”,因为无论您是读者还是作者,通过 gate1
都没有什么区别。如果在gate1
之外有一堆读者和作者,下一个进入的线程是由操作系统决定的,而不是由这个算法决定的。所以你可以把它想象成掷骰子。如果竞争 gate1
的读者和作者数量相同,那么下一个通过 gate1
的读者或作者是 50/50 的机会。
关于c++ - 如何让作家线程饿死,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38064940/