Embedded Linux: Thread Synchronization (Read-Write Locks)

Embedded Linux: Thread Synchronization (Read-Write Locks)

Click the blue text above to follow us.

In Linux, a Read-Write Lock provides a synchronization mechanism that allows multiple threads to read shared resources concurrently, but only one thread can perform write operations on that resource.

Embedded Linux: Thread Synchronization (Read-Write Locks)

Compared to mutexes or spinlocks, read-write locks offer higher concurrency because they have three states: read-locked state, write-locked state, and unlocked state.

Rules and states of read-write locks:

  • Write Mode Locked State: When a thread acquires a write lock, all other threads attempting to acquire that lock (whether read or write) will be blocked until the write lock is released.

  • Read Mode Locked State: When a thread acquires a read lock, other threads attempting to acquire a read lock can successfully acquire the lock concurrently, but any threads attempting to acquire a write lock will be blocked until all read locks are released.

Usage scenarios for read-write locks:

  • Suitable for situations where read operations are frequent and write operations are rare, allowing multiple threads to read concurrently, reducing lock contention, and improving system efficiency.

  • When there is a need to protect a shared data structure while allowing multiple threads to read, but restricting only one thread to write, read-write locks are a better choice than simple mutexes.

1

Initializing Read-Write Locks

Before using a read-write lock, it must be initialized.

Linux uses the pthread_rwlock_t data type to represent read-write locks, and there are two ways to initialize it:

Static Initialization:

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

Dynamic Initialization: Use the pthread_rwlock_init() function to initialize:

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

Parameter Description:

  • rwlock: Pointer to the read-write lock object to be initialized (type pthread_rwlock_t).

  • attr: Pointer to the read-write lock attributes (type pthread_rwlockattr_t), can be set to NULL to use default attributes.

Return Value:

  • Returns 0 on success.

  • Returns a non-zero error code on failure, such as:

    • EINVAL: Indicates an invalid attribute value or lock object.

    • EBUSY: The lock has already been initialized.

    • ENOMEM: Insufficient system memory.

2

Destroying Read-Write Locks

When a read-write lock is no longer needed, it should be destroyed using pthread_rwlock_destroy(). The function prototype is as follows:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

Parameter Description:

  • rwlock: Pointer to the read-write lock object to be destroyed.

Return Value:

  • Returns 0 on success.

  • Returns a non-zero error code on failure, such as: EBUSY: The lock is held by another thread.

3

Locking and Unlocking Read-Write Locks

To acquire a read lock, this function will block the calling thread until it can successfully acquire the read lock.

If another thread already holds the write lock, the current thread will wait.

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

Parameter Description:

  • rwlock: Pointer to the read-write lock object to be locked.

Return Value:

  • Returns 0 on success.

  • Returns a non-zero error code on failure, such as:

    • EINVAL: Invalid lock.

    • EDEADLK: Deadlock detected.

    • EAGAIN: The system cannot allocate more read locks.

To acquire a write lock, this function will block the calling thread until it can successfully acquire the write lock.

Only the current thread can acquire the write lock; all other threads requesting the lock (whether read or write) will be blocked.

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

Parameter Description:

  • rwlock: Pointer to the read-write lock object to be locked.

Return Value:

  • Returns 0 on success.

  • Returns a non-zero error code on failure, such as:

    • EINVAL: Invalid lock.

    • EDEADLK: Deadlock detected.

To attempt to acquire a read lock, this function tries to acquire a read lock without blocking.

If the lock is held by another thread, it immediately returns failure.

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

Parameter Description:

  • rwlock: Pointer to the read-write lock object to be locked.

Return Value:

  • Returns 0 on success.

  • Returns EBUSY indicating the lock is occupied and cannot be acquired at this time.

To attempt to acquire a write lock, this function tries to acquire a write lock without blocking.

If the lock is held by another thread, it immediately returns failure.

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

Parameter Description:

  • rwlock: Pointer to the read-write lock object to be locked.

Return Value:

  • Returns 0 on success.

  • Returns EBUSY indicating the lock is occupied and cannot be acquired at this time.

This function is used to release the lock held by the current thread, whether it is a read lock or a write lock.

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

Parameter Description:

  • rwlock: Pointer to the read-write lock object to be unlocked.

Return Value:

  • Returns 0 on success.

  • Returns a non-zero error code on failure, such as:

    • EINVAL: Invalid lock object.

    • EPERM: The current thread does not hold this lock.

4

Attributes of Read-Write Locks

Read-write locks can also have attributes, represented by the pthread_rwlockattr_t data type.

Function prototype for initializing read-write lock attributes:

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

Parameter Description:

  • attr: Pointer to the read-write lock attribute object to be initialized.

Return Value:

  • Returns 0 on success.

  • Returns a non-zero error code on failure.

The most common attribute of read-write locks is the process-shared attribute, which can be set or retrieved using the following function:

int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

Parameter Description:

  • attr: Pointer to the read-write lock attribute object to set.

  • pshared: Value of the shared attribute. Possible values are:

    • PTHREAD_PROCESS_SHARED: Allows multiple processes to share this read-write lock.

    • PTHREAD_PROCESS_PRIVATE: Only threads of the current process can share the read-write lock (default value).

Return Value:

  • Returns 0 on success.

  • Returns a non-zero error code on failure.

The following code demonstrates how to allow multiple threads to read shared resources concurrently while only allowing one thread to modify it under the protection of a read-write lock:

pthread_rwlock_t rwlock;int shared_data = 0;void *reader(void *arg) {    pthread_rwlock_rdlock(&rwlock);  // Acquire read lock    printf("Reader: Shared data is %d\n", shared_data);    pthread_rwlock_unlock(&rwlock);  // Unlock    return NULL;}void *writer(void *arg) {    pthread_rwlock_wrlock(&rwlock);  // Acquire write lock    shared_data += 1;    printf("Writer: Updated shared data to %d\n", shared_data);    pthread_rwlock_unlock(&rwlock);  // Unlock    return NULL;}int main() {    pthread_t r1, r2, w1;    pthread_rwlock_init(&rwlock, NULL);  // Initialize read-write lock    pthread_create(&r1, NULL, reader, NULL);    pthread_create(&w1, NULL, writer, NULL);    pthread_create(&r2, NULL, reader, NULL);    pthread_join(r1, NULL);    pthread_join(w1, NULL);    pthread_join(r2, NULL);    pthread_rwlock_destroy(&rwlock);  // Destroy read-write lock    return 0;}

Read-write locks in Linux are suitable for improving concurrency in read-intensive applications.

They allow multiple threads to read resources simultaneously, thereby reducing lock contention, but care must also be taken to consider the issue of write starvation.

Considerations are as follows:

  • Potential Issues with Read-Write Locks: If read operations are too frequent, it may lead to write threads being unable to acquire the write lock for a long time, resulting in write starvation. This often needs to be controlled through other mechanisms (such as priority).

  • Usage Scenarios: When read operations far exceed write operations, read-write locks can provide performance improvements. If write operations are frequent, read-write locks may not perform better than mutexes.

Embedded Linux: Thread Synchronization (Read-Write Locks)

Leave a Comment