有时候,goto是唯一选择
一、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,凶器也,不得已而用之。