計算機操作系統基礎(四)---進程管理之進程同步

引言

本文爲第四篇,進程管理之進程同步,本文主要介紹爲什麼需要進程間同步以及進程間同步的原則和線程同步

一、爲什麼需要進程間同步

通過兩個例子瞭解一下什麼是進程同步以及爲什麼需要進程同步

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

問題描述:有一羣生產者進程在生產產品,並將這些產品提供給消費者進程進行消費,生產者進程和消費者進程可以併發執行,在兩者之間設置了一個具有n個緩衝區的緩衝池,生產者進程需要將所生產的產品放到一個緩衝區中,消費者進程可以從緩衝區中取走產品消費

生產和消費的過程

當生產者生產了一個產品之後,緩衝區裏的產品就會+1,同樣,如果消費者從緩衝區裏邊消費一個產品,緩衝區裏的產品就會-1,在生活中這種模型是沒有問題的(比如生產手機的工廠,流水線上生產完一個手機,就會放在倉庫裏邊,消費者從倉庫中取出手機消費,這個生產者-消費者模型從宏觀的角度上看沒有問題)
在這裏插入圖片描述

上邊的模型在宏觀的角度上看沒有問題,但是在計算機微觀的角度去看就會有問題。
在計算機中,這個緩衝區是位於高速緩存或主存上邊的,如果說生產者或消費者要操作裏邊的數據時,就分爲三個步驟:

a、取出數據放到寄存器中 register=count

b、在CPU的寄存器中將register+1register=register+1 表示說生產者完成了一個產品

c、將register放回緩衝區 count=register

這三步就是就是我們操作緩衝區必須的三個步驟。我們就可以將緩衝區看作是倉庫,將register寄存器看作是生產者的地方或者消費者的地方,這個模型我們乍一看,好像也沒什麼問題
在這裏插入圖片描述

單從生產者程序或者消費者程序去看是沒問題的,但是如果兩者併發的去執行的時候就有可能出現差錯

下邊紅色部分爲生產者生產的過程,藍色的爲消費者消費的過程

將register和count看作是兩個部分的值(假設爲10), 假設此時執行生產者的第一步,也就是register=count,此時兩者均爲10,接着執行生產者的第二步,register=register+1,此時寄存器中的值+1了,那麼此時register=11,count=10,假設生產者程序和消費者程序是併發的執行的,那麼第三步就有可能輪到消費者去執行了,那麼假設此時到了消費者的第一步register=count,那麼這個時候消費者的進程裏邊的寄存器的值就是10,接着執行第四步,假設第四步執行到消費者的第二步,也就是register=register-1,此時消費者的進程的寄存器的值就變成9了,而緩存裏邊的值還是10,接着執行第五步,第五步假設執行到消費者的第三個步驟count=register,也就是把寄存器裏邊的值寫回到緩衝區裏邊,此時緩衝區和消費者的寄存器的值都是9了,這裏就完成了消費者的操作,接下來還有一個生產者的操作,將生產者的register寫回到緩衝區裏邊,那麼在剛纔,生產者的register是等於11,那麼執行完這一步,它會將register重新的寫回到緩衝區中,那麼緩衝區中得值就變成了11,count=register。那這個樣子其實就有問題了,剛開始緩衝區的值是10,而在執行的過程中進行了+1和-1的操作,那麼它的值應該還是10纔對,但是租後卻變成了11,說明這個數據是錯誤的,錯誤的原因就在於這兩個進程在併發的執行了,他們輪流的在操作緩衝區,導致緩衝區中的數據不一致,這個就是生產者-消費者的問題

下邊看一個實際執行的例子,下邊是一個簡單的程序,使用了兩個線程模擬生產者和消費者的過程:

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

pthread_ mutex t mutex = PTHREAD MUTEX INITIALIZER;
int num = 0;//全局變量,初始值爲0
void *producer(void*){//生產者
      int times = 10000000 ;
      while(times -- ){//生產者將num循環+1很多次,表示生產過程,消費者是循環-1
      //pthread mutex_ lock (&mutex) ;
      num+=1;
      //pthread mutex unlock (&mutex) ;
  }
}

void *comsumer (void* ){//消費者
      int times = 10000000 ;while(times -- ){
      //pthread mutex lock (&mutex) ;
      num  -=  1;
      //pthread mutex unlock (&mutex) ;
  }
}

//在main函數中創建了兩個線程來模擬兩個進程,一個線程執行producer的邏輯,一個線程執行comsumer的邏輯
int main()
{
	printf("Start a main function.");
	pthread_t thread1,thread1;
	pthread_create(&thread1, NULL, &producer, NULL);
	pthread_create(&thread1, NULL, &comsumer, NULL);
	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);
	printf("Print in main function: num = %d\n", num);
	retuen 0
}

因爲生產者和消費者循環的次數都是一樣的,那麼執行的結果應該是0纔對,那實際的執行結果是不是呢?我們會發現不是,那麼這個就是生產者和消費者的問題。上邊例子中的緩衝區和num就是臨界資源

(2)哲學家進餐問題

問題描述:有五個哲學家,他們的生活方式是交替的進行思考和進餐,哲學家們共同使用一張圓桌,分別坐在周圍的五張椅子上,在圓桌上有五個碗五支筷子。平時哲學家們只進行思考,飢餓時則試圖取靠近他們左、右兩支筷子,只有兩支筷子都被他拿到的時候才能進餐,進餐完畢後,放下筷子繼續思考
在這裏插入圖片描述

那麼這個過程爲什麼也需要進程同步呢?可以想象一下哲學家進餐的時候會出現什麼樣的情況,假設在這個時候某一位哲學家餓了,他需要拿起左邊的筷子和右邊的筷子進行喫飯。這個時候,第一步,拿起左邊的筷子,第二步,拿起右邊的筷子,假設此時他發現右邊的筷子被拿了,那麼他就會等待右邊的筷子釋放,筷子釋放後,他拿起右邊的筷子,開始喫飯。這就是哲學家喫飯的時候可能會面臨的問題,這樣一看,好像沒有什麼問題
在這裏插入圖片描述

看一種極端的情況,假設這五個哲學家同時肚子餓了,並且同時拿起了左邊的筷子,然後此時他們就會發現自己右邊的筷子都被拿了(可以對照上邊的圓桌圖想象一下),那麼此時,五個哲學家都會等待自己右邊的筷子被釋放,而這個時候所有的筷子都被他們自己拿起來了,所以他們都會相互等待而拿不到筷子,並且他們也都不會釋放自己左邊的筷子,因此這五個哲學家就會餓死,這個就是最極端的情況

上邊就是哲學家進餐問題,現在把筷子換成資源,把哲學家換成進程,這個就是計算機進程所面臨的問題,筷子就是臨界資源

總結一下發生上邊兩個問題的根源是什麼?

  • 根源問題是:彼此之間沒有進行通信
  • 第一個生產者-消費者的問題,我們假設生產者通知消費者我已經完成了一件生產
  • 第二個哲學家進餐問題,假設哲學家對旁邊的哲學家說我要進餐了,這個時候就不會出現問題了

因此得出結論

需要進程間的同步,那麼進程間同步是爲了解決什麼問題呢?

1、對競爭資源在多進程間進行使用次序的協調

2、使得併發執行的多個進程之間可以有效使用資源和相互合作

二、進程間同步的原則

臨界資源:臨界資源指的是一些雖作爲共享資源卻又無法同時被多個進程或線程共同訪問的共享資源。當有進程使用臨街資源時,其它進程必須依據操作系統的同步機制等待佔用進程釋放該共享資源纔可重新競爭使用共享資源

爲了對臨界資源進行有效的約束,就提出了進程間同步的四個原則

  • 空閒讓進:資源無佔用,允許使用
  • 忙則等待:資源被佔用,請求進程等待
  • 有限等待:保證有限等待時間能夠使用資源,避免其它等待的進程僵死
  • 讓權等待:等待時,進程需讓出CPU,也就是進程由執行狀態變爲阻塞狀態,這也是保證CPU可以高效使用的前提

進程間同步的方法:
消息隊列、共享存儲、信號量。會在後邊的文章中詳細介紹這些進程間同步的方法

三、線程同步

從之前的文章《進程管理之進程實體》中知道,一個進程可能會有一個或多個線程,並且線程是共享進程資源的。那麼現在就有個問題,如果多個線程併發的使用進程資源時,會發生什麼?其實也同樣會出現上邊提到的生產者-消費者問題和哲學家進餐問題,因此我們得出結論:進程內多線程也需要同步,因爲進程裏邊的線程會併發的去使用進程中的共享資源

線程同步的方法

  • 互斥量:這個是保證多線程可以互斥訪問臨界資源的一個鎖
  • 讀寫鎖:這個是應對多讀少寫或多寫少讀這種情況而發明出來的鎖
  • 自旋鎖
  • 條件變量

這些方法也會在後邊的文章詳細介紹

在快速變化的技術中尋找不變,纔是一個技術人的核心競爭力。知行合一,理論結合實踐
在這裏插入圖片描述

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