1.1 Windows線程同步
1.1.1 關鍵代碼區Critical Section
所謂“關鍵代碼區”,相信大家看名字也能理解個大概了。首先:它很關鍵,第二:它是代碼區。之所以關鍵,當然目的就是每次只能一個線程能夠進入;既然是代碼區,那就是只能在一組擁有同樣代碼的線程中用。
那什麼情況下會用到關鍵代碼區呢?當然是要保護多個線程都會用到的東西了,說到這裏,想必你已經猜到了:全局變量和靜態變量。
1.1.2 互斥Mutex
互斥看起來和關鍵代碼區是一樣的,都是每次都是隻允許一個線程使用。但互斥和關鍵代碼區相比,具有如下特點:
對比點 |
關鍵代碼區 |
互斥 |
備註 |
名字 |
無名字 |
有名字 |
NA |
跨進程 |
不能跨進程 |
可以跨進程 |
因爲有名字,所以可以跨進程 |
訪問模式 |
沒有超時 |
可以超時 |
NA |
死鎖問題 |
線程掛了其它線程就只能傻等了 |
線程掛了,操作系統會通知其它線程 |
NA |
運行環境 |
用戶區 |
內核區 |
所以關鍵代碼區性能要高一些。 |
1.1.3 信號量Semaphore
信號量本質上就是一個計數器,當計數器大於0時就意味着被保護的對象可用。每次申請計數器就減1,釋放就加1.
信號量和互斥體相比,一個最明顯的差別就在於互斥體每次只能有一個線程進行訪問,而信號量可以有多個線程進行訪問。
看到這裏,大家可能都像我開始一樣存在這樣的問題:如果將信號量最大值設置爲1,那麼不就是相當於互斥量了嗎?
看起來是一樣的,而且在有些系統上也確實是這樣的,據說是互斥體底層就是信號量來實現的,或者乾脆就沒有互斥體(例如傳統UNIX),但在有的系統上還是有差別的,差別在於:申請和釋放是否要同一個線程完成,Windows就是這種形式。互斥體要求同一線程來申請和釋放,而信號量就可以由不同的線程申請和釋放(但是我很難想象這樣做有什麼好處,難倒要給一個線程集中獲取信號量,再來通知另外的線程工作?)。
1.1.4 事件Event
事件本質上是一個系統信號,即:發生了某件事情後,發一個信號給其它關心這件事情的線程。
從事件的本質上來看,事件不是爲了資源保護的,而是爲了線程間通知用的。舉個簡單的例子:Socket接收完一個消息後,將其放入隊列,然後需要通知消息處理線程進行處理。
大家想想,如果沒有事件通知會怎麼樣呢?那接收線程只能設一個定時器或者循環,定時甚至循環去查詢隊列中是否有消息,這種定時和循環處理是對系統性能的極大浪費,所以,有了事件後,就不用這麼浪費了。
1.2 Linux線程同步
介紹完Windows,Linux介紹就很方便了,就像上一篇博文提到的一樣,Windows和Linux其實很多地方相似,線程同步也不例外。
1.2.1 關鍵代碼區???
不好意思,Linux沒有這個東東。
1.2.2 互斥Mutex
Linux和Windows是一樣的,這裏就不詳細介紹了,需要注意的是傳統UNIX並沒有互斥這個東東,傳統UNIX的互斥是通過二元信號量(即最大值爲1)來實現的。
1.2.3 信號量Semaphore
需要注意的是Linux中信號量有兩種:一種是內核POSIX標準的信號量,一種是用戶態的傳統UNIX IPC信號量。兩者的差別如下:
對比點 |
POSIX Semaphore |
IPC Semaphore |
備註 |
控制者 |
內核 |
用戶 |
IPC Semaphore可以通過semctrl函數修改對外表現。 |
權限控制 |
不允許修改 |
用戶可修改 |
NA |
性能 |
優於IPC |
劣於POSIX |
NA |
範圍 |
進程級 |
系統級 |
如果進程退出時忘記關閉,POSIX會自動釋放。 |
POSIX信號量和Windows的信號量是一樣的。
1.2.4 條件變量Conditions
看到這個名字有點莫名其妙,條件變量和線程同步有什麼關係呢?
但其實是Linux(或者是POSIX)的名字取得不好才導致我們很難理解,本質上條件變量就是Windows的事件,作用也是一樣的。唉,如果Linux或者POSIX不想和Windows同名,改成叫“通知”也能讓我們這些小蝦多省點腦力啊:)