接續上一篇分享跟執行緒相關的內容包含:安全鎖(lock)、號誌(semaphore)、前景與背景執行緒介紹等等。直接來啦~
二、安全鎖(lock)
安全鎖目的是當多個執行緒要執行同一函式時,為了避免同時進行或是影響到變數的值,因此才會有這個機制來保護。「鎖的是區塊」而非資源哦!什麼意思?來看看就知道了, 先介紹相關的一些名詞吧!
1. 互斥鎖(mutex)
由於會有多個執行緒在執行,對於全域變數的部分(也就是共享資源)就有可能會在不同的區塊同時使用到同一變數(同一資源),此時程式就很容易錯亂,就像是我們在心算時若要同時進行A+B和A-B時是不是就很容易運算錯誤呢?因為腦袋出現兩條要執行的內容並且還使用同變數,所以很容易出錯呀!電腦也是一樣的道理,因此設計了一個機制就是可以配合Lock和mutex一起使用比較不會錯亂。
結論:互斥鎖(mutex)的目的就是在同一時間限制只有一個執行緒可以使用同一變數(同一資源)不讓電腦執行錯誤,直到此執行緒釋放了這個變數(資源)才可以讓下一個執行緒來使用。(也就是排隊輪流概念)
使用互斥鎖(mutex)時會用到兩個方法它們一定是搭配使用的,也就是:
Mutex.WaitOne(); //獲取(拿取)鎖
Mutex.ReleaseMutex(); //釋放(歸還)鎖
實際看一下例子:
說明:
【程式】
第1區(也就是第173行)先建立全域變數的Mutex
第2區(第175-188行)這邊是主程式,也就是按鈕的程式
第3區(第190-200行)這邊就是在使用互斥鎖(Mutex)的方法,先拿到鎖才可以進行想要做的事,做完後就要歸還鎖。
【結果】
此時電腦跑的執行緒並不一定會乖乖從1-4慢慢執行,但是它會乖乖的將拿到鎖的執行緒把要做的事全部做完才會釋放掉,大家可以用逐步執行或設中斷點來一步步看程式是如何運作的。
以上是互斥鎖(mutex)的用法。
2. 監測執行緒(monitor)
它是安全鎖(Lock)的底層,所以很多人都說lock是monitor的「語法糖」,因為lock會對monitor的Enter和Exit進行封裝,monitor可以做的事比lock多,因為lock就只是個開關。
科普一下:
什麼是「語法糖」?
就是可以讓程式更加簡潔、提高可讀性哦!!
讓程式寫起來有甜甜的滋味,有沒有很貼心呀~哈哈
它也又稱為「糖衣語法」。
說成這樣到底monitor是有多複雜?還需要有這顆神秘魔法的語法糖來增加寫程式的甜度嗎?
還是擔心程式設計師寫到太常理性思考,感性部分都被抹除了呢?這或許是另類的貼心哦!哈哈
我們就依序慢慢看吧~就當看故事好哩!
先將互斥鎖(mutex)對應過來看監測執行緒(monitor)的狀況:
轉過來其實都差不多,幾乎可以說一樣的,只差在於它有參數而已,那哪來需要語法糖呀!真是個好問題(我也沒有深度使用過,所以目前也還不知道)
有些程式會加try...catch...finally...,這是擔心倘若中途發生異常時至少鎖要被釋放掉還給系統,為了避免此情形產生才會加進去。
那麼不是說monitor還有其他方法?是的!這邊先列出兩個方法感覺比較常用到的:
Monitor.TryEnter(object , ms );
//設置對象(object)只提供毫秒數(ms)的時間,時間道則會釋放該鎖
//這是為了避免「死鎖」的現象
Monitor.IsEntered(object);
//用來判斷目前執行緒是否保持鎖定的指定物件
OK那麼就回來看一下安全鎖Lock是怎麼使用的吧~
3. 安全鎖(lock)
說明:
【程式】
第一區塊的主程式都跟上面差不多。
第二區塊是改變最大的了,那麼上面的mutex和monitor都是需要用一對的程式去控制鎖的獲取與釋放,那Lock這邊只需要將要鎖起來部分lock起來即可。兩者的程式差異就在這裡。
【結果】
這邊一開始我用一些符號或文字來代表程式進入的狀況。
可以從結果得知UseLock_Count是先一個執行緒做完後才會有下一個執行緒進來。
~~~~~~~~~~~~~~~~~[筆記]~~~~~~~~~~~~~~~~~
上面例子尚未用到Class類別都是先以基本的先了解後往後再進階,那當使用lock時需要注意以下事項:
1.當實例是public時最好不要lock(this),因爲使用你的class的人也許不知道你用了lock,如果他new了一個實例,並且對這個實例上鎖,就很容易造成「死鎖」。
2.如果MyType是public的,不要lock(typeof(MyType))。
3.永遠也不要lock一個字符串。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
三、號誌(semaphore)
號誌可以限制多個執行緒數量同時進入函式區塊裡,它的用法要先建立建構函式:
private Semaphore semaphore1 = new Semaphore(2,5);
//第一個參數為:允許同時進入的執行緒數量
//第二個參數為:允許可容納多少執行緒數量在此區域中
//所以要注意 第一個參數 不可大於 第二參數
semaphore1 .WaitOne();
//堵住執行緒進來
//也就是說當執行緒已達到設定的數量就把門關上不讓執行緒進來,直到後面的Release()釋放執行緒後就會丟出訊號通知WaitHandle,也就是這邊WaitOne會收到訊號就會開門繼續讓執行緒進來,這一行也可以說是「守門員」
semaphore1.Release(1);
//參數是指要釋放多少個執行緒,也就是訊號出來
//功能:釋放執行緒訊號丟出訊號通知給WaitHandle
就直接先來看一下它實際怎麼運作的:
說明:
【程式】
1.第一區塊(也就是第276行)先建立建構函式
2.第二區塊就是主程式,一樣先建立執行緒,為了方便了解也設定該執行緒名字
3.第三區塊就是使用號誌的地方了,比較有問題的應該是 ((int)obj+1) ,(int)obj這個是指將obj強制轉換為int型態,轉換完後再將數值+1,那麼它與Thread.CurrentThread.Name有何不一樣?這一個是指我們主程式那邊所設定的Name名字,那 (int)obj就是電腦對此執行緒給予的一個編號。
【結果】
這邊用文字顯示讓大家看比較好懂一點程式怎麼執行的,那麼對於剩餘的執行數量這邊大家可以等程式跑完後自行畫圖,看看區塊中進入或移出的狀況,不了解也還可以自己修改號誌執行緒進入的數量再去try try看,或者也可以去修改主程式中要給予多少個執行緒,修改幾次多看幾次就會更好懂了。
這個必須動手操作才會比較好了解,否則用看的的確感覺很難,事實上動手操作一遍,再看一下每一行功能與作用就會好懂很多。
四、前景與背景執行緒
當我們使用者不管它時,它也還會在背後默默進行著直到結束,這個就稱為「背景執行緒」。用音樂來說大家一定有這個經驗想要邊上網做作業時把音樂開起後就不管它了,這個就是背景執行緒,那前景執行緒呢?
當主程式new 出一個新的執行緒此時預設皆為前景執行緒,也就是
Thread.IsBackground=false;
那麼對於執行緒來說當主程式下達了中止命令時,當前景執行緒有任一個還未完成的,它會等他完成後才會執行中止的動作。但被景執行緒就不一樣了,一旦收到中止命令時,就會直接立即中止。