Boost shared_lock. Read preferred?

后端 未结 2 1104
梦毁少年i
梦毁少年i 2020-12-08 16:11

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.

相关标签:
2条回答
  • 2020-12-08 16:57

    A google search of "boost shared lock starvation" turned up this link:

    • Example for boost shared_mutex (multiple reads/one write)?

    It looks like "upgrade" might be the key. See also:

    • Example of how to use boost upgradeable mutexes

    • http://HowardHinnant.github.io/shared_mutex

    0 讨论(0)
  • 2020-12-08 17:00

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

    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.

    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.

    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.

    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.

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

    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.

    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.

    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):

    Sometimes:

    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
    

    And sometimes:

    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
    

    And sometimes:

    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;
    }
    
    0 讨论(0)
提交回复
热议问题