死鎖、讀寫者模型實現

        在學習了線程之後,在上一博客中我模擬實現了生產者消費者模型,再模擬實現生產者消費者模型的時候我們都知道,若不加制約,可能生產者還沒生產完消費者就來消費了,所以我們就需要互斥量和條件變量來進行制約。

       而如何制約呢?莫非就是加個鎖,當生產者要生產時先去申請這個鎖,當申請到鎖時,就去進行生產,當生產完了之後就去釋放鎖,然後消費者再去申請鎖,然後再進行消費。接下來我們就來聊聊這個鎖。

      就目前我們所學的鎖來說吧,鎖分爲好幾種,一種是掛起等待鎖,一種是自旋鎖,一種是死鎖,一種是讀寫鎖。

    掛起等待鎖:當進程等待時間相對過長的時候,我們就把需要等待的進程掛起,然後當被掛起的進程要運行的條件滿足之後,它就被喚醒,然後執行自己所要完成的任務,這個就叫做掛起等待鎖。

    自旋鎖:自選鎖是當一個進程要訪問某一個進程時,那個進程此時帶着鎖,讓該進程無法訪問,然後該進程就採用輪詢的方式一直嘗試進行訪問,直到另一個的鎖釋放,然後該進程就可以進行訪問。自旋鎖是因爲進程等待的時間相對跟掛起喚醒的時間相差不多,掛起容易造成系統資源的浪費,以及死鎖的產生。

    上面我們提到了死鎖這個概念,接下來我們就來學習一下死鎖。

1、死鎖的種種

(1)死鎖的概念

      死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。

(2)死鎖產生的條件

    死鎖並不是所有的進程都會遇到的,死鎖產生也是要在一定的條件下才會產生,下面是死鎖產生的四個條件

  • 互斥條件:資源是獨佔的且排他使用,進程互斥使用資源,即任意時刻一個資源只能給一個進程使用,其他進程若申請一個資源,而該資源被另一進程佔有時,則申請者等待直到資源被佔有者釋放。
  • 不可剝奪條件進程所獲得的資源在未使用完畢之前,不被其他進程強行剝奪,而只能由獲得該資源的進程資源釋放。
  • 請求和保持條件:進程每次申請它所需要的一部分資源,在申請新的資源的同時,繼續佔用已分配到的資源。
  • 循環等待條件在發生死鎖時必然存在一個進程等待隊列{P1,P2,…,Pn},其中P1等待P2佔有的資源,P2等待P3佔有的資源,…,Pn等待P1佔有的資源,形成一個進程等待環路,環路中每一個進程所佔有的資源同時被另一個申請,也就是前一個進程佔有後一個進程所深情地資源。

以上給出了導致死鎖的四個必要條件,只要系統發生死鎖則以上四個條件至少有一個成立。事實上循環等待的成立蘊含了前三個條件的成立,似乎沒有必要列出然而考慮這些條件對死鎖的預防是有利的,因爲可以通過破壞四個條件中的任何一個來預防死鎖的發生。

(3)死鎖的預防

   我們知道不管是在線程間還是進程間死鎖都不是一定會發生的,我們知道了死鎖的產生有四個條件,那麼如果我們想預防死鎖的話,就要從死鎖產生的四個條件來着手,由於死鎖產生的第一個條件互斥條件是無法避免的,所有我們只能通過對其他三個條件進行破壞來預防死鎖。

  • 破壞“不可剝奪”條件:一個進程不能獲得所需要的全部資源時便處於等待狀態,等待期間他佔有的資源將被隱式的釋放重新加入到系統的資源列表中,可以被其他的進程使用,而等待的進程只有重新獲得自己原有的資源以及新申請的資源纔可以重新啓動,執行。
  • 破壞”請求與保持條件“:第一種方法靜態分配即每個進程在開始執行時就申請他所需要的全部資源。第二種是動態分配即每個進程在申請所需要的資源時他本身不佔用系統資源。
  • 破壞“循環等待”條件:採用資源有序分配其基本思想是將系統中的所有資源順序編號,將緊缺的,稀少的採用較大的編號,在申請資源時必須按照編號的順序進行,一個進程只有獲得較小編號的進程才能申請較大編號的進程。

2、讀寫者模型

(1)讀寫鎖

     在實現讀寫者模型之前,我們先來學習一下讀寫鎖。

     在編寫多線程的時候,有一種情況是十分常見的。那就是,有些公共數據修改的機會比較少,相對與寫,它們讀的好機會反而比寫的機會高得多。通常來說,在讀的過程中,往往伴隨着查找的操作,中間耗時很長,給這種代碼段加鎖會極大的降低我們程序的效率,所以這就需要我們下面要將的讀寫鎖(讀寫鎖實際上是一種自旋鎖)。

 當前鎖狀態讀鎖請求寫鎖請求
無鎖可以可以
讀鎖可以不可以
寫鎖不可以不可以

  注意:寫獨佔,讀共享,寫鎖優先級高

讀寫鎖接口:

 初始化:

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

銷燬:

 int pthread_rwlock_destroy(pthraed_rwlock_t *rwlock);

加鎖和解鎖:

 int pthread_rwlock_rdlock(pthraed_rwlock_t *rwlock);

 int pthread_rwlock_wrlock(pthraed_rwlock_t *rwlock);

 int pthread_rwlock_unlock(pthraed_rwlock_t *rwlock);

(2)讀寫者模型實現

       這個讀寫者模型我們也可以用前邊我們學習消費者生產者模型裏總結的“321原則”來描述。

       在讀寫者模型裏的‘321原則’解釋就是:三種關係,兩種角色,一個讀寫場所;這三種關係就是讀者與讀者之間共享關係或者說是沒什麼之間聯繫,寫者與寫者之間是互斥關係,讀者與寫者之間同步與互斥關係;兩種角色就是讀者與寫者;一個讀寫場所;

       在讀寫者模型中,讀者對於數據是隻訪問而不拿走,而在消費者生產者模型中,消費者是將數據拿走,不給別人訪問的機會。

       一般讀寫鎖有兩種策略:一是讀者優先,二是寫者優先。

接下來我們就來實現一下讀寫者模型。(不給我們這是寫者優先)

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

int counter;
pthread_rwlock_t rwlock;

void *writer(void *arg)
{
	int t;
	int i=*(int*)arg;
	free(arg);
	while(1)
	{
		t=counter;
		usleep(1000);
		pthread_rwlock_wrlock(&rwlock); //加寫鎖
		printf("writer:%d:%#x: counter=%d  ++counter=%d\n",i,pthread_self(),t,++counter);
		pthread_rwlock_unlock(&rwlock); //寫鎖取消
		usleep(5000);
	}
}

void *reader(void *arg)
{
	int t;
	int i=*(int*)arg;
	free(arg);
    while(1)
	{
		pthread_rwlock_rdlock(&rwlock); //讀者加鎖
		printf("reader:%d:%#x:counter=%d\n",i,pthread_self(),counter);
		pthread_rwlock_unlock(&rwlock);//讀者解鎖
		usleep(900);
	}
}

int main()
{
	int i;
	pthread_t tid[8];
	pthread_rwlock_init(&rwlock,NULL);

	for(i=0;i<3;i++)  //創建3個寫者
	{
		int *p=(int *)malloc(sizeof(int));
		*p=i;
		pthread_create(&tid[i],NULL,writer,(void *)p);
	}

	for(i=0;i<5;i++)   //創建5個讀者
	{
		int *p=(int *)malloc(sizeof(int));
		*p=i;
		pthread_create(&tid[i+3],NULL,reader,(void *)p);
	}

	for(i=0;i<8;i++)
	{
		pthread_join(tid[i],NULL);
	}

	pthread_rwlock_destroy(&rwlock);
        return 0;
}
運行結果如下:

以上就是死鎖和讀寫者模型的模擬實現。


  

     

     

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