操做系統原理---操做系統中進程同步和互斥的概念

簡介

    進程同步是一個操做系統級別的概念,是在多道程序的環境下,存在着不一樣的制約關係,爲了協調這種互相制約的關係,實現資源共享和進程協做,從而避免進程之間的衝突,引入了進程同步。ios

 

臨界資源

    在操做系統中,進程是佔有資源的最小單位(線程能夠訪問其所在進程內的全部資源,但線程自己並不佔有資源或僅僅佔有一點必須資源)。但對於某些資源來講,其在同一時間只能被一個進程所佔用。這些一次只能被一個進程所佔用的資源就是所謂的臨界資源。典型的臨界資源好比物理上的打印機,或是存在硬盤或內存中被多個進程所共享的一些變量和數據等(若是這類資源不被當作臨界資源加以保護,那麼頗有可能形成丟數據的問題)。算法

    對於臨界資源的訪問,必須是互訴進行。也就是當臨界資源被佔用時,另外一個申請臨界資源的進程會被阻塞,直到其所申請的臨界資源被釋放。而進程內訪問臨界資源的代碼被成爲臨界區數據庫

    對於臨界區的訪問過程分爲四個部分:編程

    1.進入區:查看臨界區是否可訪問,若是能夠訪問,則轉到步驟二,不然進程會被阻塞併發

    2.臨界區:在臨界區作操做spa

    3.退出區:清除臨界區被佔用的標誌操作系統

    4.剩餘區:進程與臨界區不相關部分的代碼線程

臨界資源使用規則:忙則等待、優先等待、空閒讓進、讓權等待(在臨界區的進程,不能在臨界區內長時間處於事件等待,必須在必定時間退出臨界區)。設計

 多個進程經常須要共同修改某些共享變量、表格、文件數據庫等,協做完成一些功能。共享協做帶來了進程的同步和互斥、死鎖、飢餓等問題。3d

進程間同步和互訴的概念

 

進程同步

    進程同步也是進程之間直接的制約關係,是爲完成某種任務而創建的兩個或多個線程,這個線程須要在某些位置上協調他們的工做次序而等待、傳遞信息所產生的制約關係。進程間的直接制約關係來源於他們之間的合做。

    好比說進程A須要從緩衝區讀取進程B產生的信息,當緩衝區爲空時,進程B由於讀取不到信息而被阻塞。而當進程A產生信息放入緩衝區時,進程B纔會被喚醒。概念如圖1所示。

    1

     圖1.進程之間的同步

 

進程互斥

    進程互斥是進程之間的間接制約關係。當一個進程進入臨界區使用臨界資源時,另外一個進程必須等待。只有當使用臨界資源的進程退出臨界區後,這個進程纔會解除阻塞狀態。

    好比進程B須要訪問打印機,但此時進程A佔有了打印機,進程B會被阻塞,直到進程A釋放了打印機資源,進程B才能夠繼續執行。概念如圖3所示。

    3

     圖3.進程之間的互斥

 

 

實現臨界區互斥的基本方法

 軟件方法

Dekker算法和peterson算法

 

 硬件實現方法

    經過硬件實現臨界區最簡單的辦法就是關CPU的中斷。從計算機原理咱們知道,CPU進行進程切換是須要經過中斷來進行。若是屏蔽了中斷那麼就能夠保證當前進程順利的將臨界區代碼執行完,從而實現了互斥。這個辦法的步驟就是:屏蔽中斷--執行臨界區--開中斷。但這樣作並很差,這大大限制了處理器交替執行任務的能力。而且將關中斷的權限交給用戶代碼,那麼若是用戶代碼屏蔽了中斷後再也不開,那系統豈不是跪了?

    還有硬件的指令實現方式,這個方式和接下來要說的信號量方式一模一樣。可是經過硬件來實現,這裏就不細說了。

 

信號量實現方式

    這也是咱們比較熟悉P V操做。經過設置一個表示資源個數的信號量S,經過對信號量S的P和V操做來實現進程的的互斥。

    P和V操做分別來自荷蘭語Passeren和Vrijgeven,分別表示佔有和釋放。P V操做是操做系統的原語,意味着具備原子性。

    P操做首先減小信號量,表示有一個進程將佔用或等待資源,而後檢測S是否小於0,若是小於0則阻塞,若是大於0則佔有資源進行執行。

    V操做是和P操做相反的操做,首先增長信號量,表示佔用或等待資源的進程減小了1個。而後檢測S是否小於0,若是小於0則喚醒等待使用S資源的其它進程。

    前面咱們C#模擬進程的同步和互斥其實算是信號量進行實現的。

 

一些經典利用信號量實現同步的問題

生產者--消費者問題

    問題描述:生產者-消費者問題是一個經典的進程同步問題,該問題最先由Dijkstra提出,用以演示他提出的信號量機制。本做業要求設計在同一個進程地址空間內執行的兩個線程。生產者線程生產物品,而後將物品放置在一個空緩衝區中供消費者線程消費。消費者線程從緩衝區中得到物品,而後釋放緩衝區。當生產者線程生產物品時,若是沒有空緩衝區可用,那麼生產者線程必須等待消費者線程釋放出一個空緩衝區。當消費者線程消費物品時,若是沒有滿的緩衝區,那麼消費者線程將被阻塞,直到新的物品被生產出來

 

    這裏生產者和消費者是既同步又互斥的關係,首先只有生產者生產了,消費着才能消費,這裏是同步的關係。但他們對於臨界區的訪問又是互斥的關係。所以須要三個信號量empty和full用於同步緩衝區,而mut變量用於在訪問緩衝區時是互斥的。

 

 

讀者--寫者問題

    問題描述:

      一個數據文件或記錄,統稱數據對象,可被多個進程共享,其中有些進程只要求讀稱爲"讀者",而另外一些進程要求寫或修改稱爲"寫者"。

      規定:容許多個讀者同時讀一個共享對象,但禁止讀者、寫者同時訪問一個共享對象,也禁止多個寫者訪問一個共享對象,不然將違反Bernstein併發執行條件。

 

    經過描述能夠分析,這裏的讀者和寫者是互斥的,而寫者和寫者也是互斥的,但讀者之間並不互斥。

    由此咱們能夠設置3個變量,一個用來統計讀者的數量,另外兩個分別用於對讀者數量讀寫的互斥,讀者和讀者寫者和寫者的互斥。如代碼4所示。

 

 

 

哲學家進餐問題

    問題描述:

    有五個哲學家,他們的生活方式是交替地進行思考和進餐。哲學家們公用一張圓桌,周圍放有五把椅子,每人坐一把。在圓桌上有五個碗和五根筷子,當一個哲學家思考時,他不與其餘人交談,飢餓時便試圖取用其左、右最靠近他的筷子,但他可能一根都拿不到。只有在他拿到兩根筷子時,方能進餐,進餐完後,放下筷子又繼續思考。

    8

    圖7.哲學家進餐問題

 

    根據問題描述,五個哲學家分別能夠看做是五個進程。五隻筷子分別看做是五個資源。只有當哲學家分別擁有左右的資源時,才得以進餐。若是不指定規則,當每一個哲學家手中只拿了一隻筷子時會形成死鎖,從而五個哲學家都由於吃不到飯而餓死。所以咱們的策略是讓哲學家同時拿起兩隻筷子。所以咱們須要對每一個資源設置一個信號量,此外,還須要使得哲學家同時拿起兩隻筷子而設置一個互斥信號量,如代碼5所示。

 

 

 

總結

    本文介紹了進程的同步和互斥的概念,臨界區的概念,以及實現進程同步互斥的方式,並解決了3種實現同步的經典問題,並給出了相應的C#模擬代碼。操做系統對於進程的管理是是計算機編程的基礎之一,所以掌握這個概念會使你的內功更上一層:-D

 

C++代碼實現生產者消費者問題

#include <iostream>
using namespace std;

int const buffersize = 5;
/* 定義進程控制塊PCB */
struct PCB
{
int flag; //flag=1 denote producer;flag=2 denote consumer;
int numlabel;
char product;
char state;
struct PCB* processlink; ////process link pointer
}*exe=0,*over=0;

PCB * readyhead = 0, * readytail = 0; /////ready queue;
PCB * consumerhead = 0,* consumertail = 0;/////consumer queue;
PCB * producerhead = 0,* producertail = 0;//////producer queue;

int productnum=0; //產品數量
int processnum=0; //進程數
int full=0,empty=buffersize; // semaphore

char buffer[buffersize]; //緩衝區
int bufferpoint=0; //緩衝區指針

void linkqueue( PCB* process, PCB* & tail );
PCB* getp(PCB* head, PCB* & tail);
int hasElement(PCB* pro);
void display(PCB* p);
void linklist(PCB* p,PCB* listhead);
void freelink(PCB* linkhead);
int processproc();
int waitfull();
int waitempty();
void signalempty();
void signalfull();
void producerrun();
void comsuerrun();

int main()
{
char begin;
int element;
cout<<"你想開始程序嗎?(y/n)";
cin>>begin;
producerhead = new PCB;
if( !producerhead ) return 1;
producertail = producerhead;
producerhead->processlink = 0;
producerhead->flag = 1;
producerhead->numlabel = processnum;

consumerhead = new PCB;
if( !consumerhead ) return 1;
consumertail = consumerhead;
consumerhead->processlink = 0;
consumerhead->flag = 2;
consumerhead->numlabel = processnum;

readyhead = new PCB;
if( !readyhead ) return 1;
readytail = readyhead;
readyhead->processlink = 0;
readyhead->flag = 3;
readyhead->numlabel = processnum;

over = new PCB;
if ( !over ) return 1;
over->processlink = 0;
while( begin=='y')
{
if( !processproc() ) break;/////
element = hasElement( readyhead );

while( element )
{
exe = getp( readyhead,readytail );
printf("進程%d申請運行,它是一個",exe->numlabel);
exe->flag==1? printf("生產者\n"):printf("消費者\n");
if( exe->flag==1 )
producerrun( );
/* comsuerrun( ); */
else
comsuerrun( );

element = hasElement(readyhead);
}

cout<<"就緒隊列中無進程"<<endl;

if( hasElement(consumerhead) )
{
cout<<"消費者等待隊列中有進程:"<<endl;
display( consumerhead );
}
else
{
cout<<"消費者等待隊列中已無進程!"<<endl;
}

if(hasElement(producerhead))
{
cout<<"生產者等待隊列中有進程:"<<endl;
display( producerhead );
}
else
{
cout<<"生產者等待隊列中已無進程!"<<endl;
}

cout<<"\n你想繼續嗎?(press /'y/' for /'n/')";
cin>>begin;
}

cout<<"\n 進程模擬完成."<<endl;
freelink(over);
over = 0;

freelink(readyhead);
readyhead = 0;
readytail = 0;

freelink(consumerhead);
consumerhead = 0;
consumertail = 0;

freelink(producerhead);
producerhead = 0;
producertail = 0;

return 0;
}

 


void linkqueue( PCB* process, PCB* & tail ) /////將進程process插入到隊列尾部
{
if( tail )
{
tail->processlink = process;
tail = process;

}
else
{
cout<<"隊列未初始化!";
exit(1);
}
}

PCB* getp(PCB* head,PCB* & tail)//////獲取進程鏈表首元素
{
PCB* p;
p = head->processlink;
if( p )
{
head->processlink = p->processlink;
p->processlink = 0;
if( !head->processlink )
tail = head;
}

else
return 0;

return p;

}


int processproc( )
{
int flag,num;
char ch;
PCB* p = 0;

cout<<"\n 請輸入但願產生的進程個數:";
cin>>num;

for(int i=0;i<num;i++)
{
cout<<"\n 請輸入您要產生的進程:輸入1爲生產者進程;輸入2爲消費者進程\n";
cin>>flag;

p = new PCB;
if( !p )
{
cout<<"內存分配失敗!"<<endl;
return 0;
}

p->flag = flag;
processnum++;
p->numlabel = processnum;
p->processlink = 0;

if( p->flag == 1 )
{
printf("您要產生的是生產者進程,它是第%d個進程,請輸入您要該進程產生的字符:\n",processnum);
cin>>ch;
p->product=ch;
productnum++;
printf("您要該進程產生的字符是%c \n",p->product);
}
else
{
printf("您要產生的是消費者進程,它是第%d個進程。\n",p->numlabel);
}

linkqueue( p , readytail);
}

return 1;
}

void signalempty( ) //////生產者隊列中有數據就放到就緒隊列中,無數據空數據加一
{
PCB* p;
if( hasElement(producerhead) )
{
p = getp( producerhead,producertail );
linkqueue( p, readytail );
printf("等待中的生產者進程進入就緒隊列,它的進程號是%d\n",p->numlabel);
}
empty++;
}

void signalfull( )/////消費者隊列中有數據就放到就緒隊列中(有無數據時)消費者進程數目full+1
{
PCB* p;
if( hasElement(consumerhead) )
{
p = getp(consumerhead,consumertail);
linkqueue(p,readytail);
printf("等待中的消費者進程進入就緒隊列,它的進程號是%d\n",p->numlabel);
}
full++;
}

void producerrun( )
{
if(!waitempty())
return;
printf("進程%d開始向緩衝區存數%c\n",exe->numlabel,exe->product);
buffer[bufferpoint] = exe->product;
bufferpoint++;
printf("進程%d向緩衝區存數操做結束\n",exe->numlabel);
signalfull();
linklist(exe,over);
}

void comsuerrun( )
{
//if(!waitfull())
// return;
//int var = bufferpoint;
//for (int i =0; i<var; i++)
//{
// printf("進程%d開始從緩衝區取數\n",exe->numlabel);
// exe->product = buffer[bufferpoint-1];
// bufferpoint--;
// exe->product = buffer[i];
// printf("進程%d從緩衝區取數操做結束,取數是%c\n",exe->numlabel,exe->product);
// signalempty();
// linklist( exe, over );
//}

if(!waitfull())
return;

printf("進程%d開始從緩衝區取數\n",exe->numlabel);
exe->product = buffer[bufferpoint-1];
bufferpoint--;

printf("進程%d從緩衝區取數操做結束,取數是%c\n",exe->numlabel,exe->product);
signalempty();
linklist( exe, over );

}

void display(PCB* p)/////查看進程中的進程類型和數目
{
p=p->processlink;
while( p )
{

p->flag==1? printf("生產者進程"):printf("消費者進程");
printf("%d\n",p->numlabel);
p = p->processlink;
}
}


int hasElement(PCB* pro)//////判讀是否有進程
{
if( !pro->processlink )
return 0;
else
return 1;
}

int waitempty()////消費者消費(完了)生成者產生的數據,值0,運行進程進入生成者等待隊列
{
if(empty<=0 )
{
printf("進程%d:緩衝區存數,緩衝區滿,該進程進入生產者等待隊列\n",exe->numlabel);
linkqueue( exe, producertail );
return 0;
}
else
{
empty--;
return 1;
}
}

int waitfull() ////生成者生成數據填滿,進程進入消費者等待隊列;
{
if(full<=0)
{
printf("進程%d:緩衝區取數,緩衝區空,該進程進入消費者等待隊列\n",exe->numlabel);
linkqueue( exe, consumertail );
return 0;
}
else
{
full--;
return 1;
}
}

void linklist(PCB* p,PCB* listhead) ////把進程p追加到listhead鏈表以後
{
PCB* cursor = listhead;
while( cursor->processlink )
{
cursor = cursor->processlink;
}
cursor->processlink = p;
}

void freelink(PCB* linkhead) { PCB* p; while( linkhead ) { p = linkhead; linkhead = linkhead->processlink; delete(p); }}

相關文章
相關標籤/搜索