• 有时候,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,凶器也,不得已而用之。

  • 相关阅读:
    js面向对象设计之function类
    js 面向对象设计之 Function 普通类
    JS 面试题 实践一
    es6面试问题——Promise
    我给出的一份前端面试题
    如何面试前端工程师?
    前端面试中的自我介绍
    第四章 --- 关于Javascript 设计模式 之 迭代器模式
    第三章 --- 关于Javascript 设计模式 之 代理模式
    第二章 --- 关于Javascript 设计模式 之 策略模式
  • 原文地址:https://www.cnblogs.com/tsecer/p/10486249.html
Copyright © 2020-2023  润新知