1. The Data Structure (pthread_rwlock_t)

We need a struct that maintains the state of the lock. We use a standard mutex to protect this struct and two condition variables to put threads to sleep.

typedef struct {
    pthread_mutex_t rw_mutex;        // Basic lock to protect this struct
    pthread_cond_t  rw_condreaders;  // Condition variable for waiting readers
    pthread_cond_t  rw_condwriters;  // Condition variable for waiting writers
    int             rw_magic;        // Magic number for error checking
    int             rw_nwaitreaders; // Number of threads waiting to read
    int             rw_nwaitwriters; // Number of threads waiting to write
    int             rw_refcount;     // -1 if writer has the lock, else # readers holding it
} pthread_rwlock_t;

Key Variable: rw_refcount

  • -1: A Writer has the lock.
  • 0: The lock is Free.
  • > 0: The number of Readers currently holding the lock.
2. Obtaining a Read Lock (rdlock)

This function attempts to get shared access.

int pthread_rwlock_rdlock(pthread_rwlock_t *rw) {
    int result;
 
    // 1. Acquire the mutex to protect the struct
    if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
        return(result);
 
    // 2. CHECK CONDITION: Can we read?
    // We loop while:
    // (a) A writer has the lock (refcount < 0)
    // OR
    // (b) A writer is WAITING (rw_nwaitwriters > 0) <-- PREFERENCE TO WRITERS
    while (rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0) {
        rw->rw_nwaitreaders++;
        result = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex);
        rw->rw_nwaitreaders--; // We woke up, so we aren't waiting anymore
        
        if (result != 0) break; // Error handling
    }
 
    // 3. Update State
    if (result == 0)
        rw->rw_refcount++;      // Success! Add ourselves to the reader count.
 
    // 4. Release the internal mutex
    pthread_mutex_unlock(&rw->rw_mutex);
    return(result);
}

Explanation:

  • The Critical Check: The line || rw->rw_nwaitwriters > 0 is the most important part. Even if the lock is currently held by readers (refcount > 0), if a writer is in the queue, this new reader puts itself to sleep. This clears the path for the writer.
  • The Wait: If we can’t read, we increment rw_nwaitreaders, sleep on rw_condreaders, and decrement it when we wake up.
3. Obtaining a Write Lock (wrlock)

This function attempts to get exclusive access.

int pthread_rwlock_wrlock(pthread_rwlock_t *rw) {
    int result;
 
    // 1. Acquire the mutex
    if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
        return(result);
 
    // 2. CHECK CONDITION: Can we write?
    // We loop while ANYONE has the lock (refcount != 0)
    while (rw->rw_refcount != 0) {
        rw->rw_nwaitwriters++;
        result = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex);
        rw->rw_nwaitwriters--;
        
        if (result != 0) break;
    }
 
    // 3. Update State
    if (result == 0)
        rw->rw_refcount = -1; // Success! Mark as locked by writer.
 
    // 4. Release the internal mutex
    pthread_mutex_unlock(&rw->rw_mutex);
    return(result);
}

Explanation: The writer is very strict. It waits if rw_refcount is anything other than 0. It demands a completely empty lock.

4. Unlocking (unlock)

This function releases the lock. It must be smart enough to know who to wake up (Reader or Writer?).

int pthread_rwlock_unlock(pthread_rwlock_t *rw) {
    int result;
 
    // 1. Acquire the mutex
    if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
        return(result);
 
    // 2. Update the Reference Count
    if (rw->rw_refcount > 0)
        rw->rw_refcount--;        // Released a read lock
    else if (rw->rw_refcount == -1)
        rw->rw_refcount = 0;      // Released a write lock
 
    // 3. WAKEUP LOGIC: Priority to Writers
    if (rw->rw_nwaitwriters > 0) {
        // If writers are waiting...
        if (rw->rw_refcount == 0) {
            // ...and the lock is now totally free, wake one writer.
            result = pthread_cond_signal(&rw->rw_condwriters);
        }
    } 
    else if (rw->rw_nwaitreaders > 0) {
        // If NO writers are waiting, wake ALL readers.
        result = pthread_cond_broadcast(&rw->rw_condreaders);
    }
 
    // 4. Release the internal mutex
    pthread_mutex_unlock(&rw->rw_mutex);
    return(result);
}

Explanation:

  1. Refcount Logic: It decrements the count. If it was a reader, the count might go from 5 to 4. If it was a writer, it goes from -1 to 0.
  2. Writer Priority: It first checks if (rw->rw_nwaitwriters > 0). If a writer is waiting, it tries to wake it up (using signal, since only one writer can run). Note: It only wakes the writer if refcount == 0. If readers still hold the lock (e.g., count went 5 4), it does not wake the writer yet, but it also does not wake any new readers.
  3. Reader Fallback: Only if no writers are waiting (else if) does it wake up the readers. It uses broadcast because multiple readers can enter simultaneously.