作業系統實驗3 經典同步互斥問題

URNOTJANET發表於2017-12-13

2017-4-21

實驗目的

通過編寫程式熟悉併發,同步/互斥,死鎖等概念

實驗內容

[基本要求]

編寫Linux環境下C程式,實現3個經典的同步/互斥問題

[具體要求]

·生產者/消費者問題

·讀者/寫者問題

·哲學家問題

·程式碼有註釋,提交實驗報告和原始碼

[進一步要求]

·多個生產者和消費者

·多個讀者和寫者,考慮讀者優先或是寫者優先

·用區別於示例程式碼的思想實現哲學家問題

·視覺化展示輸出結果

3.實驗報告

(1)生產者/消費者問題

問題思路:問題的關鍵就是要保證生產者不會在緩衝區滿時加入資料,消費者也不會在緩衝區空時消耗資料。

解決方案:

①使用linux中的pthread庫進行多執行緒程式設計,其中使用pthread_create()建立新執行緒,pthread_join()來等待執行緒結束。

②使用訊號量進行多執行緒的同步操作。

進一步說明:訊號量包括兩個操作原語:sam_wait() sam_post(),在Linux中使用sem_t表示訊號量,先使用sem_init初始化,然後使用sem_wait進行申請,使用sem_post進行釋放。

創造多個執行緒: 用pthread_t定義兩個執行緒陣列,用for迴圈語句控制,來建立多個生產者/消費者執行緒。

通過訊號量可以對共享資源進行保護。

把訊號量的值初始化成共享資源的數量,當要使用共享資源時,可以使用wait操作進行申請,只有當有剩餘資源的時候才能夠申請成功,否則要進行等待。而當使用完資源時,需要使用post操作進行釋放,使資源的數值加一,如果釋放時有人在等待資源,則可將其喚醒。

(2)讀者/寫者問題

問題描述:計算機系統中的資料(檔案、記錄)常被多個程式共享,但其中某些程式可能只要求讀資料(稱為讀者Reader);另一些程式則要求修改資料(稱為寫者Writer)。就共享資料而言,Reader和Writer是兩組併發程式共享一組資料區,要求:

①允許多個讀者同時執行讀操作;

②不允許讀者、寫者同時操作;

③不允許多個寫者同時操作。

Reader和Writer的同步問題分為讀者優先和寫者優先。

問題思路:對於讀者優先:如果當前情況下沒有寫者,就執行讀操作。【且如果之後同時存在讀者和寫者程式,優先執行讀】如果有寫者,先將當前寫操作執行完畢,然後執行讀。

對於寫者優先 :如果當前情況下沒有讀者,就執行寫操作。【且如果之後同時存在讀者和寫者程式,優先執行寫】如果有讀者,先將當前讀操作執行完畢,然後執行寫。

解決方案:①使用訊號量進行多執行緒的同步操作。②使用readcount 和writecount計算當前執行緒數,並判斷在執行之前是否存在讀/寫操作

進一步說明:兩個優先的實現方式不一樣 。

(3)哲學家就餐問題


作業系統實驗3 經典同步互斥問題
哲學家就餐問題

問題描述:一張桌上坐著5名哲學家,每兩個哲學家之間的桌上擺一根筷子,桌子的中間是一碗麵。(如圖)哲學家們只做兩件事情:思考和進餐,哲學家在思考時,並不影響他人。當哲學家飢餓的時候,會拿起左、右兩根筷子(一根一根地拿起)。如果筷子已在他人手上,則需等待。飢餓的哲學家只有同時拿到了兩根筷子才可以開始進餐,當進餐完畢後,放下筷子繼續思考。

問題思路:

5名哲學家與左右鄰居對其中間筷子的訪問是互斥關係。

可能產生死鎖,每個哲學家都拿著左手的筷子,永遠都在等右邊的筷子(或者相反)。

可能產生飢餓:有可能存在一個哲學家一直沒拿到筷子

關鍵是如何讓一個哲學家拿到左右兩個筷子而不造成死鎖或者飢餓現象。那麼解決方法有兩個,一個是讓他們同時拿兩個筷子;二是對每個哲學家的動作制定規則,避免飢餓或者死鎖現象的發生。

解決方案:設定一個服務員,一次只允許最多四個哲學家進入餐廳,保證至少有一個哲學家可以進餐,消除死鎖問題。未能進入餐廳的哲學家進入等待佇列中,根據FIFO原則,不會產生飢餓問題。

進一步說明:在實際的計算機問題中,缺乏餐叉可以類比為缺乏共享資源。一種常用的計算機技術是資源加鎖,用來保證在某個時刻資源只能被一個程式或一段程式碼訪問。當一個程式想要使用的資源已經被另一個程式鎖定,它就等待資源解鎖。當多個程式涉及到加鎖的資源時,在某些情況下就有可能發生死鎖。例如,某個程式需要訪問兩個檔案,當兩個這樣的程式各鎖了一個檔案,那它們都在等待對方解鎖另一個檔案,而這永遠不會發生。

哲學家就餐問題是在電腦科學中的一個經典問題,用來演示在平行計算中多執行緒同步時產生的問題。

4.遇到的問題

1).產生的執行緒不會結束:即mian函式無法結束,pthread_join彷彿是一個擺設.

解決方案:

查詢pthread函式庫,瞭解結束執行緒的函式有如下兩種:

pthread_join:函式pthread_join用來等待一個執行緒的結束。主執行緒中要是加了這段程式碼,就會在加程式碼的位置卡主,直到這個執行緒執行完畢才往下走。

函式原型為:

extern int pthread_join __P ((pthread_t __th, void **__thread_return));

第一個引數為被等待的執行緒識別符號,第二個引數為一個使用者定義的指標,它可以用來儲存被等待執行緒的返回值。這個函式是一個執行緒阻塞的函式,呼叫它的函式將一直等待到被等待的執行緒結束為止,當函式返回時,被等待執行緒的資源被收回。

pthread_exit:用於強制退出一個執行緒(非執行完畢退出),一般用於執行緒內部。函式原型為:

extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

唯一的引數是函式的返回程式碼,只要pthread_join中的第二個引數thread_return不是NULL,這個值將被傳遞給 thread_return.

因而只要將二者結合使用即可:

用pthread_exit線上程內退出,然後返回一個值。這個時候就跳到主執行緒的pthread_join,這個返回值會直接送到pthread_join,實現了主與分執行緒的通訊。

P.S.當然也可以設定while迴圈,計算執行時間,到達某個自定義的時間之後,強制退出,但是問題在於迴圈在哪裡設定?create之後執行緒將執行對應的函式,此時不再受主函式的控制。

2)建立執行緒時,無法對執行緒所進入的函式輸入引數

解決方案:查詢pthread_create函式如下:

定義:

int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,

(void*)(*start_rtn)(void*),void *arg);

返回值:若成功則返回0,否則返回錯誤編號

pthread_t *tidp,由tidp指向的記憶體單元被設定為新建立執行緒的執行緒ID。

const pthread_attr_t *attr,第二個引數用來設定執行緒屬性。(一般設NULL)

(void*)(*start_rtn)(void*),第三個引數是一個函式指標,即執行緒執行函式的起始地址。新建立的執行緒從start_rtn函式的地址開始執行,該函式只有一個萬能指標引數arg,如果需要向start_rtn函式傳遞的引數不止一個,那麼需要把這些引數放到一個結構中,然後把這個結構的地址作為arg的引數傳入。

void *arg,最後一個引數是執行函式的引數。(一般設NULL)

從定義可以看到,傳入的函式引數需要為void*型別,必須強制轉成對應的指標才能用。

可以傳參如下:pthread_create(&tpid,NULL,philosopher,(void *)&j);

3)讀寫者是一個一個交替進行,看不出多執行緒

解決方案:讀寫程式的懸掛時間(延遲時間)沒有設定好

相關文章