close
文章出處

多線程編程中,需要對共享變量進行加鎖。但是頻繁地加鎖,會對程序效率有很大影響。在某些讀多寫少的場景下,多個線程進行讀數據時,如果都加互斥鎖,這顯然是不必須的。于是讀寫鎖便應運而生。

讀寫鎖的加鎖規則:

1 如果沒有加寫鎖時,那么多個線程可以同時加讀鎖;如果有加寫鎖時,不可以加讀鎖

2 不管是加了讀鎖還是寫鎖,都不能繼續加寫鎖。

滿足這兩個條件,便可以初步實現一個讀寫鎖。我們用兩個鎖,一個變量,實現一個簡單的讀寫鎖,代碼如下

class rwlock
{
public:
	rwlock(): read_cnt(0)
	{
                pthread_mutex_init(&read_mtx,NULL);
                pthread_mutex_init(&write_mtx,NULL);
	}

	~ rwlock()
	{
                pthread_mutex_destroy(&read_mtx);
                pthread_mutex_destroy(&write_mtx);
	}

	void readLock()
	{
		pthread_mutex_lock(&read_mtx);
		if (++read_cnt == 1)
			pthread_mutex_lock(&write_mtx);
		pthread_mutex_unlock(&read_mtx);
	}

	void readUnlock()
	{
		pthread_mutex_lock(&read_mtx);
		if (--read_cnt == 0)
			pthread_mutex_unlock(&write_mtx);
		pthread_mutex_unlock(&read_mtx);
	}

	void writeLock()
	{
		pthread_mutex_lock(&write_mtx);
	}

	void writeUnlock()
	{
		pthread_mutex_unlock(&write_mtx);
	}

private:
	pthread_mutex_t read_mtx;
	pthread_mutex_t write_mtx;
	int read_cnt; // 讀鎖個數
};

 首先,在加讀鎖時,判斷讀者數量,如果為1,說明自己是第一個讀者,這時要加寫鎖。如果沒有寫者,加鎖成功。如果有寫者,那么需要等待寫鎖釋放。

其次,加寫鎖時,就是直接鎖write_mtx,如果沒有其他任何讀者或者寫者,加鎖成功;否則就等待write_mtx被釋放。

這種實現方法簡單明了,但是存在一個問題。當讀寫鎖被讀者占有時,這時來了寫者需要等待讀鎖釋放,如果又來了讀鎖卻可以加鎖成功。這樣就可能導致,寫鎖很難獲取,讀鎖一直無法釋放。實際應用中,我們并不期望如此,因為這有可能導致數據不能及時更新,讀取的數據是過期的。很明顯,寫鎖的優先級應該高于讀鎖。那么如何實現這樣的讀寫鎖呢?

那么在讀寫鎖的數據結構中,應該需要兩個變量,來表示在等待的讀者和寫者的數量。首先給出讀寫鎖的定義

class rwlock
{
public:
    rwlock();
    ~rwlock();
    void readlock();
    void writelock();
    void unlock();
    int tryreadlock();
    int trywritelock();
    
private:
    pthread_mutex_t rwmutex;
    int refcount;   // -1表示有寫者,0表示沒有加鎖,正數表示有多少個讀者
    int readwaiters;
    int writewaiters;
    pthread_cond_t readcond;
    pthread_cond_t writecond;
};

 實現如下:

1.構造函數,負責初始化變量

rwlock::rwlock()
{
    refcount     = 0;
    readwaiters  = 0;
    writewaiters = 0;
    pthread_mutex_init(&rwmutex,NULL);
    pthread_cond_init(&readcond, NULL);
    pthread_cond_init(&writecond, NULL);
}

 2 析構函數,銷毀資源

rwlock::~rwlock()
{
    refcount     = 0;
    readwaiters  = 0;
    writewaiters = 0;
    pthread_mutex_destroy(&rwmutex);
    pthread_cond_destroy(&readcond);
    pthread_cond_destroy(&writecond);
}

 3 加讀鎖

void rwlock::readlock()
{
    pthread_mutex_lock(&rwmutex);
    while(refcount < 0)
    {
        readwaiters++;
        pthread_cond_wait(&readcond,&rwmutex);
        readwaiters--;
    }
    refcount++;
    pthread_mutex_unlock(&rwmutex);
}

 首先,對rwmutex加鎖,主要是為了讀區refcount變量。然后在while循環中,等待讀信號量。這里要注意的是,while不能用if來判斷。我們可能會認為,在readcond有信號時,說明寫者已經釋放了寫鎖,這時refcount必然會等于0,沒必要用while循環。但是,請注意,pthread_cond_wait這個函數的執行過程。首先,它會釋放鎖rwmutex,然后等待readcond有信號,最后獲得信號量時,再對rwmutex加鎖。這樣就會存在一種情況,在readcond獲得信號之后,還沒來得及對rwmutex進行加鎖,另外一個線程這時來獲取寫鎖,很顯然它可以獲取到,refcount變成了-1。如果不對refcount進行判斷就會出錯。

4 加寫鎖

void rwlock::writelock()
{
    pthread_mutex_lock(&rwmutex);
    while(refcount != 0)
    {
        writewaiters++;
        pthread_cond_wait(&writecond,&rwmutex);
        writewaiters--;
    }
    refcount = -1;
    pthread_mutex_unlock(&rwmutex);
}

 注意點和讀鎖一樣,都是要while循環,不再重復

5釋放鎖

void rwlock::unlock()
{
    pthread_mutex_lock(&rwmutex);
    if(refcount == -1)
        refcount = 0;
    else
        refcount--;
    if(refcount == 0)
    {
        if(writewaiters > 0)
            pthread_cond_signal(&writecond);
        else if(readwaiters > 0)
            pthread_cond_broadcast(&readcond);
    }

    pthread_mutex_unlock(&rwmutex);
}

 解鎖時,如果recount==0,說明已經沒有任何人再使用讀寫鎖,那么首先判斷是否有寫鎖等待,如果是,置writecond有信號。如果沒有寫者,只有讀者,那么對readcond信號量進行廣播。

到這里,讀寫鎖的功能就介紹完了。但是注意上面的加鎖接口都是阻塞的,我們接著介紹非阻塞的加鎖接口

6 非阻塞讀鎖

int rwlock::tryreadlock()
{
    int ret = 0;
    pthread_mutex_lock(&rwmutex);
    if(refcount < 0 || writewaiters > 0)
    {
        ret = -1;
    }
    else
        refcount++;
    pthread_mutex_unlock(&rwmutex);
    return ret;
}

 

7 非阻塞寫鎖

int rwlock::trywritelock()
{
    int ret = 0;
    pthread_mutex_lock(&rwmutex);
    if(refcount != 0 )
    {
        ret = -1;
    }
    else
        refcount = -1;
    pthread_mutex_unlock(&rwmutex);
    return ret;
}

 非阻塞接口相對比阻塞接口簡單,這里就不再重復講述了。

總結:本文詳細介紹了讀寫鎖的功能,以及實現方法。實現都是基于posix接口,適用于所有類unix系統。至于windows系統,可參考這篇博文

 http://blog.csdn.net/querw/article/details/7214925


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 AutoPoster 的頭像
    AutoPoster

    互聯網 - 大數據

    AutoPoster 發表在 痞客邦 留言(0) 人氣()