Linux多線程之讀寫鎖

概述

一般大家對讀寫鎖應該有一個認知,當讀數據比修改數據頻繁,我們可以採用讀寫鎖。讀寫鎖的分配規則如下:

  1. 只要沒有線程持有某個給定的讀寫鎖用於寫時,那麼任意數目的線程可以持有該讀寫鎖用於讀;
  2. 僅當沒有線程持有某個給定的讀寫鎖用於讀或寫時,才能分配該讀寫鎖用於讀。

獲取與釋放讀寫鎖

     讀寫鎖的類型是pthread_rwlock_t,如果這個類型的某個變量是靜態分配的,那麼可以通過PTHREAD_RWLOCK_INITIALIZER來初始化它。

            pthread_rwlock_rdlock獲取一個讀出鎖,如果對應的讀寫鎖已由某個寫入者持有,那就阻塞調用線程。pthread_rwlock_wrlock獲取一個寫入鎖,如果對應的讀寫鎖已由另一個寫入者持有,或者已由一個或多個讀出者持有,那就阻塞調用線程。pthread_rwlock_unlock釋放一個讀出鎖或寫入鎖。

	pthread_rwlock_rdlock(pthread_rwlock_t *rwpt);
	pthread_rwlock_wrlock(pthread_rwlock_t *rwpt);
	pthread_rwlock_unlock(pthread_rwlock_t *rwpt);    

               下面兩個函數嘗試獲取一個讀出鎖或寫入鎖,但是如果該鎖不能馬上取得,那就返回一個EBUSY錯誤,而不是把調用線程投入睡眠。

	pthread_rwlock_tryrdlock(pthread_rwlock_t *rwpt);
	pthread_rwlock_trywrlock(pthread_rwlock_t *rwpt);

讀寫鎖屬性

       之前提到過,可通過一個靜態分配的讀寫鎖賦常值PTHREAD_RWLOCK_INITIALIZER來初始化它。讀寫鎖變量也可以通過調用pthread_rwlock_init來動態地初始化它。當一個線程不再需要某個讀寫鎖時,可以調用pthread_rwlock_destroy摧毀它。

	pthread_rwlock_init(pthread_rwlock_t *rwpt, const pthread_rwlockattr_t *attr);
	pthread_rwlock_destroy(pthread_rwlock_t *rwpt);

      初始化某個讀寫鎖時,如果attr是空指針,那就使用默認屬性。要賦予它非默認的屬性,需使用下面兩個函數。

	pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
	pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr); 

     數據類型爲pthread_rwlockattr_t的某個屬性對象一旦初始化,就通過調用不同的函數來啓用或禁止特定屬性。當前定義了唯一屬性是PTHREAD_PROCESS_SHARED,它指定相應的讀寫鎖將在不同進程間共享,而不僅僅是在單個進程內的不同線程間共享。以下兩個函數分別獲取和設置這個屬性。

	pthread_rwlockattr_getpshared(pthread_rwlockattr_t *attr, int *valptr);
	pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int value);

     第一個函數在由valptr指向的整數中返回該屬性的當前值。第二個函數把該屬性的當前值設置爲value,其值或爲PTHREAD_PROCESS_PRIVATE,或爲PTHREAD_PROCESS_SHARED。

使用互斥鎖和條件變量實現讀寫鎖

       只需要使用互斥鎖和條件變量就能實現讀寫鎖。下面將看到一種可能的實現,該實現優先考慮等待着的寫入者。當然可以有其它方案。

       首先給出pthread_rwlock.h的頭文件,它定義了基本的pthread_rwlock_t的數據類型和操作讀寫鎖的各個函數的函數原型,通常情況下它們是在<pthread.h>頭文件中。

      

#ifndef __pthread_rwlock_h
#define __pthread_rwlock_h
typedef struct
{
	pthread_mutex_t rw_mutex;
	pthread_cond_t  rw_condreaders;
	pthread_cond_t  rw_condwriters;

	int rw_magic;
	int rw_nwaitreaders;
	int rw_nwaitwriters;
	int rw_refcount;
}pthread_rwlock_t;

#define RW_MAGIC 0x19283746
#define PTHREAD_RWLOCK_INITIALIZER { PTHREAD_MUTEX_INITIALIZER,\
	PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER,\
	RW_MAGIC,0,0,0}
typedef int pthread_rwlockattr_t;

int pthread_rwlock_destroy(pthread_rwlock_t *);
int pthread_rwlock_init(pthread_rwlock_t *, pthread_rwlockattr_t* );
int pthread_rwlock_rdlock(pthread_rwlock_t *);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *);
int pthread_rwlock_trywrlock(pthread_rwlock_t *);
int pthread_rwlock_wrlock(pthread_rwlock_t *);
int pthread_rwlock_unlock(pthread_rwlock_t *);
#endif

pthread_rwlock_t數據類型含有一個互斥鎖、兩個條件變量、一個標誌及三個計數器。無論何時檢查或操作該結構,都必須持有其中的互斥鎖成員rw_mutex。該結構初始化成功後,標誌成員rw_magic就被設置成RW_MAGIC。所有函數都測試該成員,以檢查調用者是否向某個已初始化的讀寫鎖傳遞了指針。該讀寫鎖被摧毀時,這個成員就被設置爲0。

注意計數器成員之一rw_refcount總是指示着本讀寫鎖的當前狀態:-1表示它是一個寫入鎖,0表示它是可用的,大於0的值則意味着它當前容納着那麼多的讀出鎖。

下面給出phread_rwlock_int函數的實現,它動態地初始化一個讀寫鎖。

int pthread_rwlock_init(pthread_rwlock_t *rw, pthread_rwlockattr_t *attr)
{
	int result;

	if (attr != NULL)
	{
		return (EINVAL);
	}

	if ((result = pthread_mutex_init(&rw->rw_mutex, NULL)) != 0)
		goto err1;
	if ((result = pthread_cond_init(&rw->rw_condreaders, NULL)) != 0)
		goto err2;
	if ((result = pthread_cond_init(&rw->rw_condwriters, NULL)) != 0)
		goto err3;
	rw->rw_nwaitreaders = 0;
	rw->rw_nwaitwriters = 0;
	rw->rw_refcount = 0;
	rw->rw_magic = RW_MAGIC;
	return 0;
err3:
	pthread_cond_destroy(&rw->rw_condreaders);
err2:
	pthread_mutex_destroy(&rw->rw_mutex);
err1:
	return(result);
}

該函數不支持給讀寫鎖賦屬性,因此檢查其attr是否爲一個空指針。

初始化由調用者指定其指針的讀寫鎖結構中的互斥鎖和兩個條件變量的成員。所有三個計數器成員都設置爲0,rw_magic成員則設置爲表示該結構已初始化完畢的值。

如果互斥鎖或條件變量的初始化失敗,那麼小心地確保摧毀已初始化的對象,然後返回一個錯誤。

pthread_rwlock_destroy函數,當其所在的所有線程都不再持有也不試圖持有某個讀寫鎖的時候摧毀該鎖。

int pthread_rwlock_destroy(pthread_rwlock_t *rw)
{
	if (rw->rw_magic != RW_MAGIC)
		return (EINVAL);
	if ((rw->rw_refcount != 0)
		|| (rw->rw_nwaitreaders != 0)
		|| (rw->rw_nwaitwriters != 0))
	{
		return (EBUSY);
	}
	pthread_mutex_destroy(&rw->rw_mutex);
	pthread_cond_destroy(&rw->rw_condreaders);
	pthread_cond_destroy(&rw->rw_condwriters);
	rw->rw_magic = 0;
	return 0;
}

首先檢查由調用者指定的讀寫鎖已不在使用中,然後給其中的互斥鎖和兩個條件變量成員調用合適的摧毀函數。

pthread_rwlock_rdlock函數的實現如下所示:

int pthread_rwlock_rdlock(pthread_rwlock_t *rw)
{
	int result;

	if (rw->rw_magic != RW_MAGIC)
		return EINVAL;
	if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
		return result;
	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--;
		if (result != 0)
			break;
	}

	if (0 == result)
		rw->rw_refcount++;
	pthread_mutex_unlock(&rw->rw_mutex);
	return result;

}

無論何時操作pthread_rwlock_t類型的結構,都必須給其中的rw_mutex成員上鎖。

如果rw_refcount小於0,意味着當前有一個寫入者持有由調用者指定的讀寫鎖,或者有線程正等着獲取該讀寫鎖的一個寫入鎖,rw_waitwriters大於0,那麼我們無法獲取該讀寫鎖的一個讀出鎖。如果這兩個條件有一個爲真,那麼我們就把rw_waitreaders加1。並在rw_condreaders條件變量上調用pthread_cond_wait。取得讀出鎖之後,把rw_refcount加1。互斥鎖旋即釋放。

pthread_rwlock_tryrdlock函數在嘗試獲取一個讀出鎖時並不阻塞,它的實現如下:

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rw)
{
	if (rw->rw_magic != RW_MAGIC)
		return EINVAL;
	if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
		return result;
	if ((rw->rw_refcount < 0)
		|| (rw->rw_nwaitwriters > 0))
		return EBUSY;
	else
		rw->rw_refcount++;
	pthread_mutex_unlock(&rw->rw_mutex);
	return result;
}

如果當前有一個寫入者持有調用者指定的讀寫鎖,或者線程在等待該讀寫鎖的一個寫入鎖,那麼就返回EBUSY錯誤,否則,把rw_refcount加1獲取該讀寫鎖。

pthread_rwlock_wrlock函數獲取一個寫入鎖,實現如下:

int pthread_rwlock_wrlock(pthread_rwlock_t *rw)
{
	int result;

	if (rw->rw_magic != RW_MAGIC)
		return EINVAL;
	if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
		return result;
	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;
	}

	if (0 == result)
		rw->rw_refcount = -1;
	pthread_mutex_unlock(&rw->rw_mutex);
	return result;
}

只要有讀出者持有由調用者指定的讀寫鎖的讀出鎖,或者有一個寫入持有該讀寫鎖的唯一寫入鎖,這兩者情況rw_refcount都不爲0,調用線程就得阻塞,爲此,把rw_nwaitwriters加1,然後在rw_condwaitwriters條件變量上調用pthread_cond_wait,可以看到,向該條件變量發送信號的前提是,讀寫鎖被釋放並且有寫入者在等待。

取得寫入鎖之後,把rw_refcount置爲-1。

下面是pthread_rwlock_trywrlock函數的實現,該函數不是阻塞的。

int pthread_rwlock_trywrlock(pthread_rwlock_t *rw)
{
	if (rw->rw_magic != RW_MAGIC)
		return EINVAL;
	if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
		return result;
	if ( rw->rw_refcount != 0 )
		return EBUSY;
	else
		rw->rw_refcount = -1
	pthread_mutex_unlock(&rw->rw_mutex);
	return result;
}

該函數的實現和pthread_rwlock_tryrdlock很相似。

最後一個函數就是pthread_rwlock_unlock的實現:

int pthread_rwlock_unlock(pthread_rwlock_t *rw)
{
	int result;

	if (rw->rw_magic != RW_MAGIC)
		return EINVAL;
	if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
		return result;
	if (rw->rw_refcount > 0)
		rw->rw_refcount--;
	else if (-1 == rw->rw_refcount)
		rw->rw_refcount = -1;
	else
		printf("rw->rw_refcount=%d\n", rw->rw_refcount);
	if (rw->rw_nwaitwriters > 0)
	{
		if (0 == rw->rw_refcount)
			result = pthread_cond_signal(&rw->rw_condwriters);
	}
	else if (rw->rw_nwaitreaders > 0)
	{
		result = pthread_cond_broadcast(&rw->rw_condreaders);
	}
	pthread_mutex_unlock(&rw->rw_mutex);
	return result;
}

如果rw_refcount當前大於0,那麼有一個讀出者準備釋放一個讀出鎖。如果rw_refcount爲-1,那麼有一個寫入者準備釋放一個寫入鎖。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章