Three states for reader_writer locks:
1) locks in read mode
2) locks in write mode
3) unlocked mode
Only one thread can be in lock-in-write mode, but multiple threads can coexist i n lock-in-read mode.
1) If the lock is write-locked, no threads can gain the lock until it is released
2) If the lock is read-locked, threads attempting to lock it in read mode are given access, but threads attempting to lock it in write mode will be blocked.
3) read-writer usually blocks additional readers if a lock is already held in read mode and a thread is blocked trying to acquire the lock in write mode. This prevents a constant stream of readers from starving waiting writers.
Read-writer locks are well suited for situations in which data structures are read more often than they are modified.
2. System Call Definitions:
read-writer locks must be initialized before use and destroyed before freeing their underlying memory:
ubuntu@ip-172-31-23-227:~$ less /usr/include/pthread.h
......
/* Initialize read-write lock RWLOCK using attributes ATTR, or use
the default values if later is NULL. */
extern int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock,
const pthread_rwlockattr_t *__restrict
__attr) __THROW __nonnull ((1));
/* Destroy read-write lock RWLOCK. */
extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock)
__THROW __nonnull ((1));
/* Acquire read lock for RWLOCK. */
extern int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock)
__THROWNL __nonnull ((1));
/* Try to acquire read lock for RWLOCK. */
extern int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock)
__THROWNL __nonnull ((1));
/* Acquire write lock for RWLOCK. */
extern int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock)
__THROWNL __nonnull ((1));
/* Try to acquire write lock for RWLOCK. */
extern int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock)
__THROWNL __nonnull ((1));
/* Unlock RWLOCK. */
extern int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock)
__THROWNL __nonnull ((1));
......
To lock a reader-writer lock in read mode, we call pthread_rwlock_rdlock.
To write-lock mode, we call pthread_rwlock_wrlock
To unlock, we call pthread_rwlock_unlock
Similar from pthread_mutex_trylock, pthread_rwlock_tryrdlock and pthread_rwlock_trywrlock, they never blocked, but if the lock is not locked, it will lock it.
trylock is very useful for avoding the deadlock, since it never block.
2. Example:
rwlock.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
struct job {
struct job *j_next;
struct job *j_prev;
pthread_t j_id;
};
struct queue {
struct job *q_head;
struct job *q_tail;
pthread_rwlock_t q_lock;
};
// Initialize the queue by setting up the head, tail pointer
// And also init the reader-writer lock
int queue_init(struct queue *qp)
{
int err;
qp->q_head = NULL;
qp->q_tail = NULL;
err = pthread_rwlock_init(&qp->q_lock, NULL);
if(err != 0)
return err;
return 0;
}
void job_insert(struct queue *qp, struct job *jp)
{
// Lock into write-mode
pthread_rwlock_wrlock(&qp->q_lock);
// Insert the job in front of the head of queue
jp->j_next = qp->q_head;
jp->j_prev = NULL;
if(qp->q_head != NULL)
qp->q_head->j_prev = jp;
else
qp->q_tail = jp;
qp->q_head = jp;
// Unlock
pthread_rwlock_unlock(&qp->q_lock);
}
void job_append(struct queue *qp, struct job *jp)
{
// Lock into write-mode
pthread_rwlock_wrlock(&qp->q_lock);
// Append the job to the tail of queue
jp->j_next = NULL;
jp->j_prev = qp->q_tail;
if(qp->q_tail != NULL)
qp->q_tail->j_next = jp;
else
qp->q_head = jp;
qp->q_tail = jp;
// Unlock
pthread_rwlock_unlock(&qp->q_lock);
}
void job_remove(struct queue *qp, struct job *jp)
{
// Lock into the write mode
pthread_rwlock_wrlock(&qp->q_lock);
// remove the job from the queue
if(jp == qp->q_head) {
qp->q_head = jp->j_next;
if(qp->q_tail == jp)
qp->q_tail = NULL;
else
jp->j_next->j_prev = jp->j_prev;
}
else if(jp == qp->q_tail) {
qp->q_tail = jp->j_prev;
if(qp->q_head == jp)
qp->q_head = NULL;
else
jp->j_prev->j_next = jp->j_next;
}
else {
jp->j_prev->j_next = jp->j_next;
jp->j_next->j_prev = jp->j_prev;
}
// Unlock
pthread_rwlock_unlock(&qp->q_lock);
}
struct job *job_find(struct queue *qp, pthread_t id)
{
struct job *jp;
if(pthread_rwlock_rdlock(&qp->q_lock) != 0)
return NULL;
for(jp = qp->q_head; jp != NULL; jp = jp->j_next)
if(pthread_equal(jp->j_id, id))
break;
pthread_rwlock_unlock(&qp->q_lock);
return jp;
}
int main(int argc, char* argv[])
{
struct queue que;
struct job job;
queue_init(&que);
job_insert(&que, &job);
job_remove(&que, &job);
exit(0);
}
Every time when the thread operate the global struct queue, we use read-write lock to lock. In this way, we make global struct queue to be thread safe.
No comments:
Post a Comment