程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 漫談多線程(中),漫談多線程(

漫談多線程(中),漫談多線程(

編輯:C#入門知識

漫談多線程(中),漫談多線程(


這一篇接著上一篇來繼續學習多線程。

線程同步

在大多數情況下,計算機中的線程會並發運行。有些線程之間沒有聯系,獨立運行,像這種線程我們稱為無關線程。但也有一些線程,之間需要傳遞結果,需要共享資源。像這種線程,我們稱為有關線程。比如,我們網上觀看電影,一個線程負責下載電影,一個線程負責播放電影。它們只有共同合作我們才能觀看到電影,它們之間共享資源。由此,我們可以看出,線程的相關性體現在對同一資源的訪問上。我們把這種供多個線程訪問的資源成為臨界源(Critical Resource)、訪問臨界源的代碼稱為臨界區(Critical Region)。我們看個程序:

        //緩沖區,只能容納一個字符
          private static char buffer;
        static void Main(string[] args)
         {

            //線程:寫者
            Thread writer = new Thread(delegate()
            {
                string sentence = "無可奈何花落去,似曾相識燕歸來,小園香徑獨徘徊。";
                for (int i = 0; i < 24; i++)
                {
                    buffer = sentence[i]; //向緩沖區寫入字符
                    Thread.Sleep(25);
                }
            });


            //線程:讀者
            Thread Reader = new Thread(delegate()
            {
                for (int i = 0; i < 24; i++)
                {
                    char ch = buffer; //從緩存區讀取數據

                    Console.Write(ch);
                    Thread.Sleep(25);
                }
            });

            //啟動線程
            writer.Start();
            Reader.Start();
        }
    }

我們創建兩個線程,一個Writer線程負責向緩存區寫入字符,一個Reader線程負責從緩存區讀取字符。我們假設,緩存區一次只能存放一個字符。也就說,如果Reader不能及時從緩存區讀取字符,那麼就會被Writer下次要寫入的字符覆蓋掉。我們來看一下程序的運行效果,如下圖:

//緩沖區,只能容納一個字符 private static char buffer; //標識量(緩沖區中已使用的空間,初始值為0) private static long numberofUsedSpace = 0;

         static void Main(string[] args)
           {

            string sentence = "無可奈何花落去,似曾相識燕歸來,小園香徑獨徘徊。";
           
            //線程:寫者
            Thread writer = new Thread(delegate()
            {

                for (int i = 0; i < 24; i++)
                {
                    //寫入程序前檢查緩沖區中是否已滿
                    //如果已滿,就進行等待。如果未滿,就寫入字符.
                    while (Interlocked.Read(ref numberofUsedSpace) == 1)
                    {
                        Thread.Sleep(10);
                    }
                    buffer = sentence[i]; //向緩沖區寫入字符
                    Thread.Sleep(25);

                    //寫入數據後,將numberofUsedSpace由0變1
                    Interlocked.Increment(ref numberofUsedSpace);
                }
            });


            //線程:讀者
            Thread Reader = new Thread(delegate()
            {
                for (int i = 0; i < 24; i++)
                {
                    //讀取之前檢查緩沖區是否已滿
                    //如果已滿,進行讀取,如果未滿,進行等待。
                    while (Interlocked.Read(ref numberofUsedSpace) == 0)
                    {
                        Thread.Sleep(25);
                        
                    }
                    char ch = buffer; //從緩存區讀取數據
                    Console.Write(ch);
                    
                    //讀取完字符,將numberofUsedSpace由1設為0
                    Interlocked.Decrement(ref numberofUsedSpace);
                }
            });

            //啟動線程
            writer.Start();
            Reader.Start();
        }

我們通過一個numerofUsedSpace的變量作為計數器,假設numberofUsedSpace=1已滿,numberofUsedSpace=0未滿。每當Writer線程向緩存區寫入字符時,需要通過Interlocked的Read方法來檢查numberofUsedSpace是否已滿。如果未滿,吸入字符,如果已滿,進行等待。同樣,當Read線程需要向緩沖區讀取字符時,也是通過Interlocked的Rread方法來檢查numberofUsedSpace是否已滿,已滿,進行讀取,未滿進行等待。

 

管程(Monitor類)

另一種實現線程同步的方法,是通過Monitor類。看程序:

        //緩沖區,只能容納一個字符
        private static char buffer;
        //用於同步的對象(獨占鎖)
        private static object lockForBuffer = new object();
        static void Main(string[] args)
        {
            //線程:寫者
            Thread writer = new Thread(delegate()
            {
                string sentence = "無可奈何花落去,似曾相識燕歸來,小園香徑獨徘徊。";

                for (int i = 0; i < 24; i++)
                {
                    try
                    {
                        //進入臨界區
                        Monitor.Enter(lockForBuffer);

                        buffer = sentence[i]; //向緩沖區寫入字符

                        //喚醒睡眠在臨界資源上的線程
                        Monitor.Pulse(lockForBuffer);

                        //讓當前的線程睡眠在臨界資源上
                        Monitor.Wait(lockForBuffer);

                    }
                    catch (ThreadInterruptedException)
                    {
                        Console.WriteLine("線程writer被中止");
                    }
                    finally
                    {
                        //推出臨界區
                        Monitor.Exit(lockForBuffer);
                    }

                }
            });


            //線程:讀者
            Thread Reader = new Thread(delegate()
            {
                for (int i = 0; i < 24; i++)
                {
                    try
                    {
                        //進入臨界區
                        Monitor.Enter(lockForBuffer);
                        char ch = buffer; //從緩存區讀取數據
                        Console.Write(ch);

                        //喚醒睡眠在臨界資源上的線程
                        Monitor.Pulse(lockForBuffer);

                        //讓當前線程睡眠在臨界資源上
                        Monitor.Wait(lockForBuffer);
                    }
                    catch (ThreadInterruptedException)
                    {
                        Console.WriteLine("線程reader被中止");
                    }
                    finally
                    {
                        //退出臨界區
                        Monitor.Exit(lockForBuffer);
                    }
                }
            });

            //啟動線程
            writer.Start();
            Reader.Start();
        }
     

當線程進入臨界區,會調用Monitor的Entry方法來獲取獨占鎖,如果得到,就進行操作,如果被別的線程占用,就睡眠在臨界資源上,直到獨占鎖被釋放。如果此時,別的線程進入臨界區,會發現獨占鎖被占用,他們會睡眠在臨界資源上。Monitor會記錄有哪些線程睡眠在臨界資源上,當線程執行完操作,調用Pulse()方法,喚醒睡眠在臨界資源上的線程。因為,線程還需要下次操作,所以需要調用Wait()方法,令自己睡眠在臨界資源上。最後通過調用Exit()方法釋放獨占鎖。

Note that:Monitor只能鎖定引用類型的變量,如果使用值類型變量。每調用一次Entry()方法,就進行一次裝箱操作,每進行一次裝箱操作就會得到一個新的object對象。相同的操作執行在不同的對象上,得不得同步的效果。為了確保退出臨界區時臨界資源得到釋放,我們應把Monitor類的代碼放入Try語句,把調用Exit()方法放入finally語句。為了方便,C#為我們提供了更加簡潔的語句。

lock(要鎖定的對象)
{
//臨界區的代碼
    。。。。。
   。。。。。。
}

lock語句執行完,會自動調用Exit()方法,來釋放資源。它完全等價於:

try
{
     Monitor.Entry(要鎖定的對象);
      //臨界區代碼
    。 。。。。。
   。。。。。。
   。。。。。。
}
finally
{
    Monitor.Exit(要鎖定的對象);
}

當線程以獨占鎖的方式去訪問資源時,其他線程是不能訪問的。只有當lock語句結束後其他線程才可以訪問。從某種方面可以說,lock語句相當於暫定了程序的多線程功能。這就相當於在資源上放了一把鎖,其他線程只能暫定,這樣會使程序的效率大打折扣。所以只有必要時才可以設置獨占鎖。(我們回想一下Interlocked,當通過Interlocked的Read()方法來讀取計數器,如果不符合條件,就會等待,線程狀態變為SleepWaitJoin狀態。但是,Monitor的Entry()方法獲取獨占鎖,如果得不到,線程會被中止,狀態會變為Stopped。這是二者的一點區別)。

互斥體(Mutex類)

  

在操作系統中,線程往往需要共享資源,而這些資源往往要求排他性的使用。即一次只能由一個線程使用,這種排他性的使用資源稱為線程之間的互斥(Mutual Exclusion)。互斥,從某種角度也起到了線程同步的目的,所以互斥是一種特殊的同步。與Monitor類似,只有獲得Mutex對象的所屬權的線程才可以進入臨界區,沒有獲得所屬權的只能在臨界區外等候。使用Mutex要比使用Monitor消耗資源。但它可以在系統中的不同程序間實現線程同步。

互斥分為局部互斥、系統互斥。顧名思義,局部互斥,只在創建的程序中有效。系統互斥,會在整個系統中有效。

看程序:

         static  void Main(string[] args)
        {

            Thread threadA = new Thread(delegate()
                {
                    //創建互斥體
                    Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");

                    string fileName = @"E:\TimeRecord.txt";
                    for (int i = 1; i <= 10; i++)
                    {
                        try
                        {
                            //請求互斥體的所屬權,若成功,則進入臨界區,若不成功,則等待
                            fileMutex.WaitOne();

                            //在臨界區中操作臨界資源,即向文件中寫入數據
                            File.AppendAllText(fileName, "threadA: " + DateTime.Now + "\r\n");
                        }
                        catch (ThreadInterruptedException)
                        {
                            Console.WriteLine("線程A被中斷。");
                        }
                        finally
                        {
                            fileMutex.ReleaseMutex();  //釋放互斥體的所屬權
                        }
                        Thread.Sleep(1000);
                    }
                });
            threadA.Start();
        }

 

static void Main(string[] args)
        {
            Thread threadB = new Thread(delegate()
            {
                //創建互斥體
                Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");

                string fileName = @"E:\TimeRecord.txt";
                for (int i = 1; i <= 10; i++)
                {
                    try
                    {
                        //請求互斥體的所屬權,若成功,則進入臨界區,若不成功,則等待。
                        fileMutex.WaitOne();

                        //在臨界區中操作臨界資源,即向文件中寫入數據
                        File.AppendAllText(fileName, "ThreadB: " + DateTime.Now + "\r\n");

                    }
                    catch (ThreadInterruptedException)
                    {
                        Console.WriteLine("線程B被中斷.");
                    }
                    finally
                    {
                        fileMutex.ReleaseMutex(); //釋放互斥體的所屬權
                    }
                    Thread.Sleep(1000);
                }
            });

            threadB.Start();

            Process.Start("MutecA.exe"); //啟動程序MutexA.exe
        }

這是兩個程序,我們創建了一個系統互斥體(“MutexForTimeRecordFile”,在整個系統中有效,可以跨程序)。在程序B中有執行程序A的代碼,通過編譯,我們把兩個編譯後的可執行文件放在一起,執行B。效果如下圖:

QQ Photo20140822170857

通過效果圖,我們可以看出,兩個不同的程序通過相同的系統互斥體名,達到了跨程序線程同步的效果。

我們來總結一下C#為我們帶來的這三種實現多線程同步的類。

1.Interlocked類

是通過調用Read()方法,來讀取計數器,判斷計數器,以此達到線程同步的目的。

2.Monitor類

通過調用Entry()方法來獲取獨占鎖,當執行完代碼,要調用Pulse()方法喚醒睡眠在臨界資源上的線程,同時自己調用Wait()方法,睡眠在臨界資源,以便下次訪問臨界區。Monitor與Interlocked的不同點是,當調用Monitor.Entry()方法,未獲得獨占鎖時,線程狀態會變為Stopped。而Interlocked正是將線程處於SleepWaitJoin狀態。

3.Mutex類

調用Mutex的WaitOne()方法來獲取所屬權,獲得所屬權的線程可以進入臨界區,沒有所屬權的線程在臨界區外等待。Mutex對象比Monitor對象消耗資源,但是Mutex對象可以實現跨程序的線程同步。

Mutex分為局部互斥、系統互斥。

 


java多線程中比較全面的方法與功可以注釋,有實例是最好的了,

淺談java內存模型
不同的平台,內存模型是不一樣的,但是jvm的內存模型規范是統一的。java的多線程並發問題最終都會反映在java的內存模型上,所謂線程安全無非要控制多個線程對某個資源的有序訪問或修改。java的內存模型,要解決兩個主要的問題:可見性和有序性。我們都知道計算機有高速緩存的存在,處理器並不是每次處理數據都是取內存的。JVM定義了自己的內存模型,屏蔽了底層平台內存管理細節,對於java開發人員,要解決的是在jvm內存模型的基礎上,如何解決多線程的可見性和有序性。
那麼,何謂可見性? 多個線程之間是不能互相傳遞數據通信的,它們之間的溝通只能通過共享變量來進行。Java內存模型(JMM)規定了jvm有主內存,主內存是多個線程共享的。當new一個對象的時候,也是被分配在主內存中,每個線程都有自己的工作內存,工作內存存儲了主存的某些對象的副本,當然線程的工作內存大小是有限制的。當線程操作某個對象時,執行順序如下:
(1) 從主存復制變量到當前工作內存 (read and load)
(2) 執行代碼,改變共享變量值 (use and assign)
(3) 用工作內存數據刷新主存相關內容 (store and write) JVM規范定義了線程對主存的操作指令:read,load,use,assign,store,write。當一個共享便變量在多個線程的工作內存中都有副本時,如果一個線程修改了這個共享變量,那麼其他線程應該能夠看到這個被修改後的值,這就是多線程的可見性問題。
那麼,什麼是有序性呢 ?線程在引用變量時不能直接從主內存中引用,如果線程工作內存中沒有該變量,則會從主內存中拷貝一個副本到工作內存中,這個過程為read-load,完成後線程會引用該副本。當同一線程再度引用該字段時,有可能重新從主存中獲取變量副本(read-load-use),也有可能直接引用原來的副本 (use),也就是說 read,load,use順序可以由JVM實現系統決定。
線程不能直接為主存中中字段賦值,它會將值指定給工作內存中的變量副本(assign),完成後這個變量副本會同步到主存儲區(store- write),至於何時同步過去,根據JVM實現系統決定.有該字段,則會從主內存中將該字段賦值到工作內存中,這個過程為read-load,完成後線程會引用該變量副本,當同一線程多次重復對字段賦值時,比如:
for(int i=0;i<10;i++)
a++;
線程有可能只對工作內存中的副本進行賦值,只到最後一次賦值後才同步到主存儲區,所以assign,store,weite順序可以由JVM實現系統決定。假設有一個共享變量x,線程a執行x=x+1。從上面的描述中可以知道x=x+1並不是一個原子操作,它的執行過程如下:
1 從主存中讀取變量x副本到工作內存
2 給x加1
3 將x加1後的值寫回主 存
如果另外一個線程b執行x=x-1,執行過程如下:
1 從主存中讀取變量x副本到工作內存
2 給x減1
3 將x減1後的值寫回主存
那麼顯然,最終的x的值是不可靠的。假設x現在為10,線程a加1,線程b減1,從表面上看,似乎最終x還是為10,但是多線程情況下會有這種情況發生:
1:線程a從主存讀取x副本到工作內存,工作內存中x值為10
2:線程b從主存讀取x副本到工作內存,工作內存中x值為10
3:線程a將工作內存中x加1,工作內存中x值為11
4:線程a將......余下全文>>
 

C語言中的線程?

給你推薦一些比較好的教程吧,你應該用得著: 漫談C++ Builder多線程編程技術: www.it55.com/...5.html 用MFC編寫多線程程序實例: www.it55.com/...7.html C++寫的web服務器程序(多線程): www.it55.com/...9.html 後面兩個都是多線程的實例教程。
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved