[實踐] 單例程式模式

樑濤發表於2012-10-12

有時候,我們需要限制某個程式只啟動單一例項程式以實現單例模式(如守護程式)。那麼,在UNIX/Linux環境下,有多少種方法可以實現這一需求?它們的可靠性如何?下面是一些總結心得。

[方法一:使用ps命令對當前同名程式計數]
互斥執行最可靠的辦法是使用互斥鎖。但是跨程式使用互斥鎖是個相當麻煩的事情,再者,某些古舊的UNIX上並沒有此類鎖。於是最土的解決方法是使用ps命令列出當前同名命令列條目並計數,如果數值大於1,則表示程式已經啟動過一次,當前程式可以安心退出。

以經典Shell程式設計為例,最常見的對同名命令列條目計數的寫法是:

count=`ps -ef | grep xxxx.sh | grep -v grep | wc -l`

首先使用ps -ef列出所有程式,然後grep出同名命令列條目(即含有“xxxx.sh”字串的條目,預設按行分隔),同時排除掉grep命令自己的條目,最後使用wc -l完成計數。相當直白的程式碼,一目瞭然,對不對?很可惜,這個寫法有非常隱晦的Bug。

Shell通過呼叫fork()來生成子程式,並在子程式中呼叫exec()來替換執行具體命令(如ps和grep命令,程式號不變),從而建立起完整的管道線。Bug產生自a) fork()出的子程式在呼叫exec()之前,ps命令列出的子程式命令列條目與父程式命令列條目相同,同時b) fork()和exec()不是在一次原子呼叫中完成的。在fork()之後、exec()之前,作業系統剛好排程ps程式執行的話,那麼得到的結果很可能包含2~4個含有“xxxx.sh”字串的條目,這將導致count值不準確,最終執行與預期不相符的後續程式碼。

在這裡,分析Bug的要點是,管道線的各個組成命令不是序列建立和執行的,當執行結果依賴於特定的執行順序時,併發或並行執行會產生微妙的競態條件。避免方法也很簡單,只要打破這一依賴即可:

list=`ps -ef`
count=`echo "${list}" | grep xxxx.sh | grep -v grep | wc -l`

在上述程式碼中,ps -ef執行時fork()和exec()早已完成,不存在因子程式未替換執行具體命令所帶來的同名條目,若有,則可以肯定是在其它終端以相同命令列啟動了同一Shell程式所致,從而避免了計數不準確的問題。

(未完待續……)

相關文章