尝试使用 protothreads

  cheney

    最近正想把开发板一直开着,作为我的闹钟用,这是个简单的任务,似乎没有挑战性啊,于是尝试用上 protothreads .之前看过 protothreads 的文章,感觉似乎很简单,又似乎很玄妙.这次真正用过了,才知道,真是疯狂的 duff’ device .

    我用了这么久 C ,写代码一直是中规中矩的.没有这样用过 switch-case 语句:

    	switch( k )
    	{
    		case 1:
    		break;
    		
    		for(i =0 ; i<n ; i++ )
    		{
    			case 2:
    			continue;
    		
    			case 3:
    			break;
    		}
    		
    		case 4:
    		break;
    	}
    

    类似这样的程序,编译竟然不报错.你知道,它怎样运行么?

    好吧,测试之后,我终于知道, switch-case 语句的 case 是可以放在任意多的嵌套里面的,知道不是函数跳转就都可以.

    基于这个机制,再加上编译器的一个特殊的宏 LINE ,就可以组成 protothreads 了.

    看看 protothreads 的函数写法.

    static int
    protothread2(struct pt *pt)
    {
      PT_BEGIN(pt);
    
      while(1) {
    
        PT_WAIT_UNTIL(pt, protothread1_flag != 0);
        printf("Protothread 2 running\n");
        
      }
      PT_END(pt);
    }
    

    看一下原始定义:

    #define LC_INIT(s) s = 0;
    #define LC_RESUME(s) switch(s) { case 0:
    #define LC_SET(s) s = __LINE__; case __LINE__:
    #define LC_END(s) }
    
    #define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc)
    
    #define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \
    					PT_INIT(pt); return PT_ENDED; }
    

    用这些宏将 PT_BEGIN PT_END 展开就是:

    static int
    protothread2(struct pt *pt)
    {
    	{
    	  	char PT_YIELD_FLAG = 1;
    	  	switch((pt)->lc)
    	  	{
    	  		case 0:
    	  		
    			while(1) {
    	
    			    PT_WAIT_UNTIL(pt, protothread1_flag != 0);
    			    printf("Protothread 2 running\n");
    	  		}
    	  		
    	  		
    	  	LC_END((pt)->lc);
    	  	PT_YIELD_FLAG = 0; 
    		PT_INIT(pt); 
    		return PT_ENDED; 
    	}
    }
    
    

    开启来有点 duff’ device 的样子了, 粗看之下似乎都不和语法. 继续展开, 将 PT_WAIT_UNTIL , 和其中的 LC_SET 也展开.

    #define PT_WAIT_UNTIL(pt, condition)            \
    	do {                                        \
    	LC_SET((pt)->lc);                           \
    		if(!(condition)) {                          \
    			return PT_WAITING;                      \
    		}                                           \
    	} while(0)
    
    static int
    protothread2(struct pt *pt)
    {
    	{
    	  	char PT_YIELD_FLAG = 1;
    	  	switch((pt)->lc)
    	  	{
    	  		case 0:
    	  		
    			while(1) {
    	
    			    do {                        
    					
    					(pt)->lc = __LINE__;
    					
    					case __LINE__:
    					
    					if(!(protothread1_flag))
    					{
    						return PT_WAITING;
    					}
    					
    				} while(0)
    	
    			    printf("Protothread 2 running\n");
    	  		}
    	  		
    	  	}
    	  	PT_YIELD_FLAG = 0; 
    		PT_INIT(pt); 
    		return PT_ENDED; 
    	}
    }
    

    这下明了了, 说白了, 就是等待信号的时候,其实是将行号保存到开始传进去的那个结构保存起来,然后函数就返回了,下次再调用就从返回的地方继续了.(这种任务函数必须是在循环里反复调用的)

    我的闹钟已经写完了,用着还好.这种调用方式让我很轻松就实现了并行的任务,以后可能在项目中不会这样用,但是这个想法确实是相当有趣的.