進程同步 & 互斥

原文鏈接:http://www.cnblogs.com/CareySon/archive/2012/04/14/Process-SynAndmutex.html


簡介

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

 

臨界資源

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

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

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

    1.進入區:查看臨界區是否可訪問,如果可以訪問,則轉到步驟二,否則進程會被阻塞

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

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

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

 

進程間同步和互訴的概念

 

進程同步

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

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

    1

     圖1.進程之間的同步

 

     用C#代碼模擬進程之間的同步如代碼1所示。

 class ProcessSyn
    {
        private static Mutex mut = new Mutex();
        
        
        static void Main()
        {
            Console.WriteLine("進程1執行完了進程2才能執行.......");
            Thread Thread1 = new Thread(new ThreadStart(Proc1));
            Thread Thread2 = new Thread(new ThreadStart(Proc2));
            Thread1.Start();
            Thread2.Start();
            Console.ReadKey();   
        }
       
        private static void Proc1()
        {
            mut.WaitOne();
            Console.WriteLine("線程1執行操作....");
            Thread.Sleep(3000);
            mut.ReleaseMutex();//V操作

        }
        private static void Proc2()
        {
            

            mut.WaitOne();//P操作
            Console.WriteLine("線程2執行操作....");
            mut.WaitOne();
        }
    }

    代碼1.C#模擬進程之間的同步

 

    運行結果如圖2所示。

    2

    圖2.運行結果

 

進程互斥

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

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

    3

     圖3.進程之間的互斥

 

     用C#模擬進程之間的互斥,這裏我啓動了5個線程,但同一時間內只有一個線程能對臨界資源進行訪問。如代碼2所示。

class ProcessMutex
    {
        private static Mutex mut = new Mutex();
        private const int numThreads = 5;
        
        static void Main()
        {
            
            for (int i = 0; i <= numThreads; i++)
            {
                Thread myThread = new Thread(new ThreadStart(UseResource));
                myThread.Name = String.Format("線程{0}", i + 1);
                myThread.Start();
            }
            Console.ReadKey();
            
        }
        
        
        //同步
        private static void UseResource()
        {
            // 相當於P操作
            mut.WaitOne();

            
            /*下面代碼是線程真正的工作*/
            Console.WriteLine("{0}已經進入臨界區",
                Thread.CurrentThread.Name);
            Random r = new Random();
            int rNum = r.Next(2000);
            
            Console.WriteLine("{0}執行操作,執行時間爲{1}ms", Thread.CurrentThread.Name,rNum);
            Thread.Sleep(rNum);

            Console.WriteLine("{0}已經離開臨界區\r\n",
                Thread.CurrentThread.Name);
            /*線程工作結束*/


            
            // 相當於V操作
            mut.ReleaseMutex();
        }
        //互斥
  


    }

    代碼2.C#模擬進程之間的互斥

 

     運行結果如圖4所示。

    4   

    圖4.C#模擬進程互斥

 

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

 硬件實現方法

    通過硬件實現臨界區最簡單的辦法就是關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變量用於在訪問緩衝區時是互斥的。

    利用C#模擬生產者和消費者的關係如代碼3所示。

    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()
         {
             Console.WriteLine("生產者消費者模擬......");
             for (int i = 1; i < 9; i++)
             {
                 Thread Thread1 = new Thread(new ThreadStart(Producer));
                 Thread Thread2 = new Thread(new ThreadStart(Customer));
                 Thread1.Name = String.Format("生產者線程{0}", i);
                 Thread2.Name = String.Format("消費者線程{0}", i);
                 Thread1.Start();
                 Thread2.Start();
             }
             Console.ReadKey();

         }
         
         private static void Producer()
         {
             Console.WriteLine("{0}已經啓動",Thread.CurrentThread.Name);
             empty.WaitOne();//對empty進行P操作
             mut.WaitOne();//對mut進行P操作
             Console.WriteLine("{0}放入數據到臨界區", Thread.CurrentThread.Name);
                 Thread.Sleep(1000);
             mut.ReleaseMutex();//對mut進行V操作
             full.Release();//對full進行V操作
         }
         private static void Customer()
         {
             Console.WriteLine("{0}已經啓動", Thread.CurrentThread.Name);
             Thread.Sleep(12000);
             full.WaitOne();//對full進行P操作
             mut.WaitOne();//對mut進行P操作
             Console.WriteLine("{0}讀取臨界區", Thread.CurrentThread.Name);
             mut.ReleaseMutex();//對mut進行V操作
             empty.Release();//對empty進行V操作
         }
    }

   代碼3.使用C#模擬生產者和消費者的關係

 

    運行結果如圖5所示。

    5

     圖5.生產者消費者C#模擬結果

 

讀者--寫者問題

    問題描述:

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

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

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

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

class ReaderAndWriter
    {
        private static Mutex mut = new Mutex();//用於保護讀者數量的互斥信號量
        private static Mutex rw = new Mutex();//保證讀者寫者互斥的信號量

        static int count = 0;//讀者數量
        
        
        static void Main()
        {
            Console.WriteLine("讀者寫者模擬......");
            for (int i = 1; i < 6; i++)
            {

                Thread Thread1 = new Thread(new ThreadStart(Reader));
                Thread1.Name = String.Format("讀者線程{0}", i);
                Thread1.Start();
                
            }
            Thread Thread2 = new Thread(new ThreadStart(writer));
            Thread2.Name = String.Format("寫者線程");
            Thread2.Start();
            Console.ReadKey();


        }
        
        private static void Reader()
        {
            
            mut.WaitOne();
            if (count == 0)
            {
                rw.WaitOne();
            }
            count++;
            mut.ReleaseMutex();
            
            Thread.Sleep(new Random().Next(2000));//讀取耗時1S
            Console.WriteLine("讀取完畢");
            
            mut.WaitOne();
            count--;
            mut.ReleaseMutex();
            if (count == 0)
            {
                rw.ReleaseMutex();
            }

        }
        private static void writer()
        {
            
            rw.WaitOne();
            Console.WriteLine("寫入數據");
            rw.ReleaseMutex();
    
        }

   代碼4.C#模擬讀者和寫者問題

 

    運行結果如圖6所示。

    

    圖6.讀者寫者的運行結果

 

哲學家進餐問題

    問題描述:

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

    8

    圖7.哲學家進餐問題

 

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

class philosopher
    {
        private static int[] chopstick=new int[5];//分別代表哲學家的5只筷子
        private static Mutex eat = new Mutex();//用於保證哲學家同時拿起兩雙筷子
        static void Main()
        {
            //初始設置所有筷子可用
            for (int k = 1; k <= 5; k++)
            {
                chopstick[k - 1] = 1;
            }
            //每個哲學家輪流進餐一次
            for(int i=1;i<=5;i++)
            {
                Thread Thread1 = new Thread(new ThreadStart(Philosophers));
                Thread1.Name = i.ToString();
                Thread1.Start();
            }
            Console.ReadKey();
        }
        private static void Philosophers()
        {
            
            //如果筷子不可用,則等待2秒
            while (chopstick[int.Parse(Thread.CurrentThread.Name)-1] !=1 || chopstick[(int.Parse(Thread.CurrentThread.Name))%4]!=1)
            {
                Console.WriteLine("哲學家{0}正在等待", Thread.CurrentThread.Name);
                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);
            Console.WriteLine("哲學家{0}正在用餐...",Thread.CurrentThread.Name);
            //用餐完畢後放下筷子
            chopstick[int.Parse(Thread.CurrentThread.Name)-1] = 1;
            chopstick[(int.Parse(Thread.CurrentThread.Name)) % 4] = 1;
            Console.WriteLine("哲學家{0}用餐完畢,繼續思考", Thread.CurrentThread.Name);
        }
    }

    代碼5.C#模擬哲學家用餐問題

 

    運行結果如圖7所示。

    7

    圖8.哲學家問題運行結果

 

 

總結

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

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