read/write lock implementation using mutex only?

2019-03-11 17:40发布

问题:

I was trying to implement read/write lock using mutex only (just for learning). Just when i thought i have covered all corner cases (as the program worked with variety of combinations), i have realized, i ignored the fact (as it worked in ubuntu) that, the mutex should be freed by the owner of the thread. Below is my implementation,

class rw_lock_t{

    int NoOfReaders;
    int NoOfWriters, NoOfWritersWaiting;
    pthread_mutex_t class_mutex;
    pthread_cond_t class_cond;
    pthread_mutex_t data_mutex;

public:

    rw_lock_t()
    : NoOfReaders(0),
      NoOfWriters(0), NoOfWritersWaiting(0)
    {
            pthread_mutex_init(&class_mutex, NULL);
            pthread_mutex_init(&data_mutex, NULL);
            pthread_cond_init(&class_cond, NULL);
    }
    void r_lock()
    {
            pthread_mutex_lock(&class_mutex);
            //while(NoOfWriters!=0 || NoOfWritersWaiting!=0) //Writer Preference
            while(NoOfWriters!=0)
            {
                    pthread_cond_wait(&class_cond, &class_mutex);
            }
            if(NoOfReaders==0)
            {
                    pthread_mutex_unlock(&class_mutex);
                    pthread_mutex_lock(&data_mutex);
                    pthread_mutex_lock(&class_mutex);
                    NoOfReaders++;
                    pthread_mutex_unlock(&class_mutex);
            }
            else if(NoOfReaders>0) //Already Locked
            {
                    NoOfReaders++;
                    pthread_mutex_unlock(&class_mutex);
            }
    }
    void w_lock()
    {
            pthread_mutex_lock(&class_mutex);
            NoOfWritersWaiting++;
            while(NoOfReaders!=0 && NoOfWriters!=0)
            {
                    pthread_cond_wait(&class_cond, &class_mutex);
            }
            pthread_mutex_unlock(&class_mutex);

            pthread_mutex_lock(&data_mutex);
            pthread_mutex_lock(&class_mutex);
            NoOfWritersWaiting--; NoOfWriters++;
            pthread_mutex_unlock(&class_mutex);
    }
    void r_unlock()
    {
            pthread_mutex_lock(&class_mutex);
            NoOfReaders--;
            if(NoOfReaders==0)
                    pthread_mutex_unlock(&data_mutex);
            pthread_mutex_unlock(&class_mutex);
            pthread_cond_signal(&class_cond);
    }
    void w_unlock()
    {
            pthread_mutex_lock(&class_mutex);
            NoOfWriters--;
            if(NoOfWriters==0)
                    pthread_mutex_unlock(&data_mutex);
            pthread_mutex_unlock(&class_mutex);
            pthread_cond_signal(&class_cond);
    }
};

My question now is, what is the best way (minimal change) to rectify. Semaphore is definitely the idle choice, but I thought of solutions as below

Solution#1

1) I will have a dedicated thread, just to lock/unlock the mutex for read case.

2) This thread will be waiting on a condition variable to get signal from r_lock or r_unlock.

3) r_lock and r_unlock will instead of doing "pthread_mutex_lock/unlock(&data_mutex);", will signal the dedicated thread to lock instead.

4) I have to remember many facts for this implementation,

  • The signaling and actual locking are two different events, so might need synchronization.

  • Will need a mutex+condVariable+thread and more synchronization extra.

Update: Solution#2

1) The thread who did the actual locking will keep its tid globally.

2) whenever a thread unlocks will make sure the check equality with the global tid.

3) If matches will wait for "NoOfReaders==0" condition and unlock it.

So, is there a better way in which the program can be rectified.

回答1:

You do not need a separate mutex "for data"; the whole construct will serve as the data lock, if its internal logic is correct. Instead, you could use two separate condition variables for readers and for writers, so that you can broadcast all waiting readers without affecting waiting writers. The code is below; you can also see that it's simpler this way. Besides, I added a destructor and fixed a bug in w_lock: the condition to wait should be (NoOfReaders!=0 || NoOfWriters!=0), and not &&.

class rw_lock_t {

    int NoOfReaders;
    int NoOfWriters, NoOfWritersWaiting;
    pthread_mutex_t class_mutex;
    pthread_cond_t  reader_gate;
    pthread_cond_t  writer_gate;

public:

    rw_lock_t()
    : NoOfReaders(0), NoOfWriters(0), NoOfWritersWating(0),
      class_mutex(PTHREAD_MUTEX_INITIALIZER),
      reader_gate(PTHREAD_COND_INITIALIZER),
      writer_gate(PTHREAD_COND_INITIALIZER)
    {}
    ~rw_lock_t()
    {
        pthread_mutex_destroy(&class_mutex);
        pthread_cond_destroy(&reader_gate);
        pthread_cond_destroy(&writer_gate);
    }
    void r_lock()
    {
        pthread_mutex_lock(&class_mutex);
        //while(NoOfWriters>0 || NoOfWritersWaiting>0) //Writer Preference
        while(NoOfWriters>0)
        {
            pthread_cond_wait(&reader_gate, &class_mutex);
        }
        NoOfReaders++;        
        pthread_mutex_unlock(&class_mutex);
    }
    void w_lock()
    {
        pthread_mutex_lock(&class_mutex);
        NoOfWritersWaiting++;
        while(NoOfReaders>0 || NoOfWriters>0)
        {
            pthread_cond_wait(&writer_gate, &class_mutex);
        }
        NoOfWritersWaiting--; NoOfWriters++;
        pthread_mutex_unlock(&class_mutex);
    }
    void r_unlock()
    {
        pthread_mutex_lock(&class_mutex);
        NoOfReaders--;
        if(NoOfReaders==0 && NoOfWritersWaiting>0)
            pthread_cond_signal(&writer_gate);
        pthread_mutex_unlock(&class_mutex);
    }
    void w_unlock()
    {
        pthread_mutex_lock(&class_mutex);
        NoOfWriters--;
        if(NoOfWritersWaiting>0)
            pthread_cond_signal(&writer_gate);
        //else //Writer Preference - don't signal readers unless no writers
        pthread_cond_broadcast(&reader_gate);
        pthread_mutex_unlock(&class_mutex);
    }
};


回答2:

class ReadWriteLock {
    mutex writeLock;
    mutex readLock;
    int readCount;
public:
    ReadWriteLock() {
        readCount = 0;
    }
    void LockWrite() {
        writeLock.lock();
    }
    void UnlockWrite() {
        writeLock.unlock();
    }
    void LockRead() {
        lock_guard<mutex> lock(readLock);
        ++readCount;
        if (1 == readCount) {
            LockWrite();
        }
    }
    void UnlockRead() {
        lock_guard<mutex> lock(readLock);
        --readCount;
        if (0 == readCount) {
            UnlockWrite();
        }
    }
};

As Alexey pointed out, if the last read thread to UnlockWrite isn't the first read thread to LockWrite, the behavior is undefined. See std::mutex::unlock http://www.cplusplus.com/reference/mutex/mutex/unlock/ Windows ReleaseMutex: http://msdn.microsoft.com/en-us/library/windows/desktop/ms685066(v=vs.85).aspx