進程同步
進程同步也是進程之間直接的制約關系,進程間的直接制約關系來源於他們之間的合作。比如說進程A需要從緩沖區讀取進程B產生的信息,當緩沖區為空時,進程B因為讀取不到信息而被阻塞。而當進程A產生信息放入緩沖區時,進程B才會被喚醒。
進程互斥
進程互斥是進程之間的間接制約關系。當一個進程進入臨界區使用臨界資源時,另一個進程必須等待。
實現進程同步和互斥的基本方法
法I:硬件實現方法——關CPU的中斷。CPU進行進程切換是需要通過中斷來進行,如果屏蔽了中斷那么就可以保證當前進程順利的將臨界區代碼執行完,從而實現了互斥。這個辦法的步驟就是:屏蔽中斷–執行臨界區–開中斷。但這樣做並不好,這大大限制了處理器交替執行任務的能力。並且將關中斷的權限交給用戶代碼,那么如果用戶代碼屏蔽了中斷后不再開,那系統豈不是跪了?
法II:軟件實現方式——信號量
這也是我們比較熟悉P V操作。
- P:荷蘭語Passeren,表示占有(資源)。
- P(S):①將信號量S的值減1,即S=S-1;②如果S>=0,則該進程繼續執行;否則該進程置為等待狀態,排入等待隊列。
- V:荷蘭語Vrijgeven,表示釋放(資源)。
- V(S):①將信號量S的值加1,即S=S+1;②如果S>0,則該進程繼續執行;否則釋放隊列中第一個等待信號量的進程。
- S: Semaphore,信號量。當S>0時,S表示當前可用資源的數量;當S<0時,其絕對值表示等待使用該資源的進程個數。
S如果只能=0或1,表示對於臨界區的保護,這時的P操作和V操作有以下兩種實現方式
- critical section(臨界區)
- mutex(互斥器)
定義寫法 |
HANDLE hmtx; |
CRITICAL_SECTION cs; |
初始化寫法 |
hmtx= CreateMutex (NULL, FALSE, NULL); |
InitializeCriticalSection(&cs); |
結束清除寫法 |
CloseHandle(hmtx); |
DeleteCriticalSection(&cs); |
無限期等待的寫 法(P操作) |
WaitForSingleObject (hmtx, INFINITE); |
EnterCriticalSection(&cs); |
0等待(狀態檢測) 的寫法(P操作) |
WaitForSingleObject (hmtx, 0); |
TryEnterCriticalSection(&cs); |
任意時間等待的 寫法(P操作) |
WaitForSingleObject (hmtx, dwMilliseconds); |
不支持 |
V操作的寫法 |
ReleaseMutex(hmtx); |
LeaveCriticalSection(&cs); |
優點 |
可以跨進程 |
速度快。因為Critical Section不是內核對象,函數EnterCriticalSection()和LeaveCriticalSection()的調用一般都在用戶模式內執行。 |
缺點 |
速度慢。Mutex 是內核對象,相關函數的執行 (WaitForSingleObject,ReleaseMutex)需要用戶模式(User Mode)到內核模式(Kernel Mode)的轉換,在x86處理器上這種轉化一般要發費600個左右的 CPU指令周期。 |
只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。 |
例1:生產者和消費者的關系
class ProducerAndCustomer { private static Mutex mut = new Mutex(); //臨界區信號量 private static Semaphore empty = new Semaphore(5, 5); private static Semaphore full = new Semaphore(0, 5); static void Main() { Thread Thread1 = new Thread(new ThreadStart(Producer)); Thread Thread2 = new Thread(new ThreadStart(Customer)); Thread1.Start(); Thread2.Start(); } private static void Producer() { empty.WaitOne(); //對empty進行P操作 mut.WaitOne(); //對mut進行P操作(拿走一個空瓶子) //數據放入臨界區... Thread.Sleep(12000); mut.ReleaseMutex(); //對mut進行V操作 full.Release(); //對full進行V操作(放入一個滿瓶子) } private static void Customer() { Thread.Sleep(12000); full.WaitOne();//對full進行P操作 mut.WaitOne(); //對mut進行P操作 //讀取臨界區... mut.ReleaseMutex(); //對mut進行V操作 empty.Release(); //對empty進行V操作 } }
例2:讀者–寫者問題
規定:允許多個讀者同時讀一個共享對象,但禁止讀者、寫者同時訪問一個共享對象,也禁止多個寫者訪問一個共享對象
分析: 這里的讀者和寫者是互斥的,而寫者和寫者也是互斥的,但讀者之間並不互斥。 由此我們可以設置3個變量,一個用來統計讀者的數量,另外兩個分別用於讀寫的互斥,寫者和寫者的互斥。以下程序假設只有一個寫程序,所以沒有聲明寫者與寫者互斥的信號量。class ReaderAndWriter { private static Mutex mut = new Mutex();//用於保護讀者數量的互斥信號量 private static Mutex rw = new Mutex(); //保證讀者寫者互斥的信號量 static int count = 0;//讀者數量 static void Main() { for(int i = 0; i < 5; i++) { Thread Thread1 = new Thread(new ThreadStart(Reader)); Thread1.Start(); } Thread Thread2 = new Thread(new ThreadStart(writer)); Thread2.Start(); } private static void Reader() { mut.WaitOne(); if (count == 0) { rw.WaitOne(); } count++; mut.ReleaseMutex(); Thread.Sleep(new Random().Next(2000)); //Read... mut.WaitOne(); count--; mut.ReleaseMutex(); if (count == 0) { rw.ReleaseMutex(); } } private static void writer() { rw.WaitOne(); //Write... rw.ReleaseMutex(); } }
例3:哲學家進餐問題
有五個哲學家,他們的生活方式是交替地進行思考和進餐。他們公用一張圓桌,在圓桌上有五個碗和五根筷子,哲學家飢餓時便試圖取用其左、右最靠近他的筷子,只有在他拿到兩根筷子時,方能進餐,進餐完后,放下筷子又繼續思考。
class philosopher { private static int[] chopstick=new int[5];//分別代表哲學家的5只筷子 private static Mutex eat = new Mutex();//用於保證哲學家同時拿起兩雙筷子 static void Main() { //初始設置所有筷子可用 for (int k = 0; k < 5; k++) chopstick[k]=1; //每個哲學家輪流進餐一次 for(int i=1;i<=5;i++){ Thread Thread1 = new Thread(new ThreadStart(Philosophers)); Thread1.Name = i.ToString(); Thread1.Start(); } } private static void Philosophers() { //如果筷子不可用,則等待2秒 while (chopstick[int.Parse(Thread.CurrentThread.Name)-1] !=1 || chopstick[(int.Parse(Thread.CurrentThread.Name))%4]!=1) { Thread.Sleep(2000); } eat.WaitOne(); //同時拿起兩雙筷子 chopstick[int.Parse(Thread.CurrentThread.Name)-1] = 0; chopstick[(int.Parse(Thread.CurrentThread.Name)) % 4] = 0; eat.ReleaseMutex(); Thread.Sleep(1000); //正在用餐... chopstick[int.Parse(Thread.CurrentThread.Name)-1] = 1; chopstick[(int.Parse(Thread.CurrentThread.Name)) % 4] = 1; } }
spin lock(自旋鎖)
自旋鎖是專為防止多處理器並發而引入的一種鎖。無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,但是兩者在調度機制上略有不同。對於互斥鎖,如果資源已經被占用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那里看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名。