提升 shared_lock.阅读首选?

2021-12-24 00:00:00 c++ boost

我正在检查读/写锁的 boost 库(1.45 版).当我对其进行测试时,似乎 shared_ptr 更喜欢我的读者线程,即当我的作者试图为它的操作获取锁时,它并没有阻止任何后续读取的发生.

I was checking out the boost library(version 1.45) for a reader/writer lock. When I ran my tests on it, it seemed like the shared_ptr was preferring my reader threads, i.e. when my writer tried to take the lock for its operation, it didn't stop any subsequent reads from occurring.

有可能在 boost 中改变这种行为吗?

Is it possible in boost to change this behavior?

using namespace std;
using namespace boost;

mutex outLock;
shared_mutex workerAccess;
bool shouldIWork = true;

class WorkerKiller
{
public:   

    void operator()()  
    {
        upgrade_lock<shared_mutex> lock(workerAccess); 
        upgrade_to_unique_lock<shared_mutex> uniqueLock(lock);

        cout << "Grabbed exclusive lock, killing system" << endl;
        sleep(2);
        shouldIWork = false;
        cout << "KILLING ALL WORK" << endl;  
    }  

private:  
};

class Worker
{  
public:   

    Worker()
    {  
    }  

    void operator()()  
    {
        shared_lock<shared_mutex> lock(workerAccess); 

        if (!shouldIWork) {
            outLock.lock();
            cout << "Workers are on strike.  This worker refuses to work" << endl;
            outLock.unlock();
        } else {
            sleep(1);

            outLock.lock(); 
            cout << "Worked finished her work" << endl;
            outLock.unlock(); 
        }
    }  
};  

int main(int argc, char* argv[])  
{  
    Worker w1;
    Worker w2;
    Worker w3;
    Worker w4;
    WorkerKiller wk;

    boost::thread workerThread1(w1);
    boost::thread workerThread2(w2);

    boost::thread workerKillerThread(wk);

    boost::thread workerThread3(w3);
    boost::thread workerThread4(w4);

    workerThread1.join();
    workerThread2.join();
    workerKillerThread.join();
    workerThread3.join();

    return 0;
}

这是每次的输出:

工作完成了她的工作
工作完成了她的工作
工作完成了她的工作
工作完成了她的工作
抢到排他锁,杀死系统
杀死所有工作

Worked finished her work
Worked finished her work
Worked finished her work
Worked finished her work
Grabbed exclusive lock, killing system
KILLING ALL WORK

我的要求

如果作者试图获取排他锁,我希望所有先前的读取操作都完成.然后所有后续的读操作都被阻塞.

If the writer tried to grab an exclusive lock, I'd like for all previous read operations to finish. And then all subsequent read operations to block.

推荐答案

我回答这个问题有点晚了,但我相信我有一些相关的信息.

I'm a little late to this question, but I believe I have some pertinent information.

shared_mutex 向 C++ 委员会提出的提案,即 boost 库所基于的,故意没有指定 API 来为读者和作者提供优先权.这是因为 Alexander Terekhov 大约在十年前提出了一个完全公平的算法.它让操作系统决定下一个获取互斥锁的线程是读取器还是写入器,操作系统完全不知道下一个线程是读取器还是写入器.

The proposals of shared_mutex to the C++ committee, which the boost libs are based on, purposefully did not specify an API to give readers nor writers priority. This is because Alexander Terekhov proposed an algorithm about a decade ago that is completely fair. It lets the operating system decide whether the next thread to get the mutex is a reader or writer, and the operating system is completely ignorant as to whether the next thread is a reader or writer.

因为有了这个算法,不再需要指定是首选读者还是作者.据我所知,boost 库现在(boost 1.52)已经用这个公平的算法实现了.

Because of this algorithm, the need for specifying whether a reader or writer is preferred disappears. To the best of my knowledge, the boost libs are now (boost 1.52) implemented with this fair algorithm.

Terekhov 算法的读/写互斥锁由两个门组成:gate1 和 gate2.一次只能有一个线程可以通过每个门.可以使用互斥锁和两个条件变量来实现门.

The Terekhov algorithm consists of having the read/write mutex consist of two gates: gate1 and gate2. Only one thread at a time can pass through each gate. The gates can be implemented with a mutex and two condition variables.

读者和作者都试图通过gate1.为了通过gate1,必须确保写入线程当前不在gate1 内部.如果存在,则尝试通过 gate1 的线程会阻塞.

Both readers and writers attempt to pass through gate1. In order to pass through gate1 it must be true that a writer thread is not currently inside of gate1. If there is, the thread attempting to pass through gate1 blocks.

一旦读取线程通过 gate1,它就拥有了互斥锁的读取所有权.

Once a reader thread passes through gate1 it has read ownership of the mutex.

当写线程通过gate1时,它也必须通过gate2才能获得互斥锁的写所有权.直到gate1里面的读者数量降为零后才能通过gate2.

When a writer thread passes through gate1 it must also pass through gate2 before obtaining write ownership of the mutex. It can not pass through gate2 until the number of readers inside of gate1 drops to zero.

这是一个公平的算法,因为当 gate1 内只有 0 个或更多读取器时,由操作系统决定下一个进入 gate1 的线程是读取器还是写入器.一个写者只有在通过了 gate1 后才被优先",因此是下一个获得互斥锁所有权的人.

This is a fair algorithm because when there are only 0 or more readers inside of gate1, it is up to the OS as to whether the next thread to get inside of gate1 is a reader or writer. A writer becomes "prioritized" only after it has passed through gate1, and is thus next in line to obtain ownership of the mutex.

我使用了您的示例,该示例是针对 C++14 中最终成为 shared_timed_mutex 的示例实现编译的(对您的示例进行了细微修改).下面的代码将其称为 shared_mutex,这是它在提出时的名称.

I used your example compiled against an example implementation of what eventually became shared_timed_mutex in C++14 (with minor modifications to your example). The code below calls it shared_mutex which is the name it had when it was proposed.

我得到以下输出(都具有相同的可执行文件):

I got the following outputs (all with the same executable):

有时:

Worked finished her work
Worked finished her work
Grabbed exclusive lock, killing system
KILLING ALL WORK
Workers are on strike.  This worker refuses to work
Workers are on strike.  This worker refuses to work

有时:

Worked finished her work
Grabbed exclusive lock, killing system
KILLING ALL WORK
Workers are on strike.  This worker refuses to work
Workers are on strike.  This worker refuses to work
Workers are on strike.  This worker refuses to work

有时:

Worked finished her work
Worked finished her work
Worked finished her work
Worked finished her work
Grabbed exclusive lock, killing system
KILLING ALL WORK

我相信理论上也应该可以获得其他输出,尽管我没有通过实验证实这一点.

I believe it should be theoretically possible to also obtain other outputs, though I did not confirm that experimentally.

为了充分披露,这是我执行的确切代码:

In the interest of full disclosure, here is the exact code I executed:

#include "../mutexes/shared_mutex"
#include <thread>
#include <chrono>
#include <iostream>

using namespace std;
using namespace ting;

mutex outLock;
shared_mutex workerAccess;
bool shouldIWork = true;

class WorkerKiller
{
public:   

    void operator()()  
    {
        unique_lock<shared_mutex> lock(workerAccess); 

        cout << "Grabbed exclusive lock, killing system" << endl;
        this_thread::sleep_for(chrono::seconds(2));
        shouldIWork = false;
        cout << "KILLING ALL WORK" << endl;  
    }  

private:  
};

class Worker
{  
public:   

    Worker()
    {  
    }  

    void operator()()  
    {
        shared_lock<shared_mutex> lock(workerAccess); 

        if (!shouldIWork) {
            lock_guard<mutex> _(outLock);
            cout << "Workers are on strike.  This worker refuses to work" << endl;
        } else {
            this_thread::sleep_for(chrono::seconds(1));

            lock_guard<mutex> _(outLock);
            cout << "Worked finished her work" << endl;
        }
    }  
};  

int main()  
{  
    Worker w1;
    Worker w2;
    Worker w3;
    Worker w4;
    WorkerKiller wk;

    thread workerThread1(w1);
    thread workerThread2(w2);

    thread workerKillerThread(wk);

    thread workerThread3(w3);
    thread workerThread4(w4);

    workerThread1.join();
    workerThread2.join();
    workerKillerThread.join();
    workerThread3.join();
    workerThread4.join();

    return 0;
}

相关文章