一、goto情節
goto或許相當於白堊紀時期的恐龍,曾經橫行於整個地球,但是它的命運和和恐龍一樣,最後逐漸絕跡。Dijstra老師第一個對goto拍案而起,痛陳該指令的危害,正如我們現在看有些代碼的感受:寫代碼的人爽了,維護的人哭了。曾經抓住BASIC語言的尾巴,見到過早期的BASIC語言寫的程式,那個感覺和過山車類似,突然間就跑到幾丈開外。
後來大家就習慣了對goto的歧視,雖然核心中經常可以見到這些語句,但是據說是為了提高效率,同樣是有情可原,因為畢竟不是太懂,人家寫代碼的人甩我幾條街應該是沒有問題的,所以這裡也不置可否。但是這裡說的不是效率問題,而是真心必須用goto,雖然看起來非常詭異,但是這個東西的確是工程中存在的一個例子,而不是我捏造的例子。
二、pthread_cleanup_push
這個是posix線程庫中的一個標準介面,就是為了向線程註冊自己的一個遺願,如果說自己在接下來的執行中,執行到對應的pthread_cleanup_pop之前不幸被pthread_cancel取消,請系統幫我執行一下註冊的回呼函數,否則系統可能會出現不一致。畢竟一個線程掛掉之後,其它的線程可能還要繼續頑強的跑下去。
這裡最為典型的例子就是互斥鎖,當一個線程可能會獲得一個鎖,然後開始執行,但是這個執行時間比較長,並且在執行過程中被pthread_cancel殺死,那麼這個鎖算是報廢了,更為嚴重的是這裡將會成為一個線程黑洞,所有的線程執行到這裡的時候都將會掛起,並且永遠不會被喚醒。所以對於一些關鍵的操作,線程都會註冊對這個鎖的解鎖回呼函數。
進一步說,對於一個介面可能是比較關鍵,不容有任何閃失,所以它壓根不受這個鎖的限制,所以它可以跳過這個互斥鎖的擷取動作。大家可能會覺得奇怪,怎麼會有這種事情,畢竟說這個介面可能會被用來複位系統,如果說萬一哪裡出現鎖異常,那麼複位操作就會失敗,這對於互動式系統沒有關係,大家大不了按電源就好了,但是對嵌入式系統來說,它就悲劇了(如果看門狗還正常工作的話),它將成為成為一個殭屍系統,也就是雖然在跑,但是功能紊亂,並且不能複位。
綜上所述,這裡的模型代碼就是醬紫的:
[tsecer@Harry gotoOnly]$ cat gotoOnly.c
#include <pthread.h>
int doSomething(int lockfree)
{
static pthread_mutex_t locker = PTHREAD_MUTEX_INITIALIZER;
/*Here we will get the lock*/
if(!lockfree)
{
pthread_cleanup_push((void(*)(void*))pthread_mutex_unlock,(void*)&locker);
pthread_mutex_lock(&locker);
}
/*Here may do a lot of stuff*/
{
extern void foo(void);
}
/*Here starts the cleanup*/
if(!lockfree)
{
pthread_cleanup_pop(1);
}
}
代碼的邏輯就是在某些情況下才執行pthread_cleanup_push/pthread_cleanup_pop操作,又是後並不執行,所以把它們放在一個if條件中,但是這個代碼是無法通過編譯的。
三、編譯錯誤
編譯這個代碼,提示的錯誤有些無厘頭,因為代碼裡並沒有使用任何的while操作。
[tsecer@Harry gotoOnly]$ gcc gotoOnly.c -c
gotoOnly.c: In function ‘doSomething’:
gotoOnly.c:12: error: expected ‘while’ before ‘_INIT'
所以這裡一定是預先處理之後的問題,我們看一下預先處理的結果
[tsecer@Harry gotoOnly]$ tail -n 20 gotoOnly.c.i
# 2 "gotoOnly.c" 2
int doSomething(int lockfree)
{
static pthread_mutex_t locker = { { 0, 0, 0, 0, 0, { 0 } } };
/*Here we will get the lock*/
if(!lockfree)
{ 注意:這裡的左括弧和下面相同顏色的右括弧匹配
do { __pthread_unwind_buf_t __cancel_buf; void (*__cancel_routine) (void *) = ((void(*)(void*))pthread_mutex_unlock); void *__cancel_arg = ((void*)&locker); int not_first_call = __sigsetjmp ((struct __jmp_buf_tag
*) (void *) __cancel_buf.__cancel_jmp_buf, 0); if (__builtin_expect (not_first_call, 0)) { __cancel_routine (__cancel_arg); __pthread_unwind_next (&__cancel_buf); } __pthread_register_cancel (&__cancel_buf);
do {;
pthread_mutex_lock(&locker);
}
/*Here may do a lot of stuff*/
{
extern void foo(void);
}
/*Here starts the cleanup*/
if(!lockfree)
{
do { } while (0); } while (0); __pthread_unregister_cancel (&__cancel_buf); if (1) __cancel_routine (__cancel_arg); } while (0);
}
}
可以看到這裡已經完全錯亂了,所以編不過是最好的,如果這裡沒有錯誤而編譯出可執行檔,那問題定位起來必定將會更加複雜。
四、man手冊關於該函數的說明
POSIX.1 permits pthread_cleanup_push() and pthread_cleanup_pop() to be
implemented as macros that expand to text containing '{' and '}',
respectively. For this reason, the caller must ensure that calls to
these functions are paired within the same function, and at the same
lexical nesting level. (In other words, a clean-up handler is only
established during the execution of a specified section of code.)
pthread_cleanup_pop和pthread_cleanup_push必須在同一個函數,並且必須在同一個詞法域。這是一個極強的限制,所以上面的代碼有語法錯誤,而glibc也的確是把這兩個函數作為宏來實現了。
五、goto登場
修改之後代碼
[tsecer@Harry gotoOnly]$ cat gotoOnlygoto.c
#include <pthread.h>
int doSomething(int lockfree)
{
static pthread_mutex_t locker = PTHREAD_MUTEX_INITIALIZER;
/*Here we will get the lock*/
if(lockfree)
goto lockskipped;
pthread_cleanup_push((void(*)(void*))pthread_mutex_unlock,(void*)&locker);
pthread_mutex_lock(&locker);
lockskipped:
/*Here may do a lot of stuff*/
{
extern void foo(void);
}
/*Here starts the cleanup*/
if(lockfree)
goto unlockskipped;
pthread_cleanup_pop(1);
unlockskipped:
0;
}
[tsecer@Harry gotoOnly]$ gcc gotoOnlygoto.c -c
[tsecer@Harry gotoOnly]$
正如常言說的: goto,兇器也,不得已而用之。
轉自:http://tsecer.blog.163.com/blog/static/1501817201221882145262/