阻塞代码和非阻塞代码的区别

最近在看傻孩子的一本书,里面有很多值得学习的东西。在学习的过程中还是要注重做笔记。

阻塞指的就是死等在这里,以 LED 闪烁为例,我们来说明一下阻塞代码和非阻塞代码的区别。

首先,我们以阻塞代式来编写一个延时函数:

//! 阻塞形式的延时代码
void delay( uint32_t wTime )
{
	while(wTime--) 
	{
		NOP(); //!<加入 nop 可以有效防止编译器对改循环进行优化
	}
}

接下来,我们在超级循环中实现一个简单的 LED 闪烁功能:

extern void led_on(void); //!<硬件无关的 LED 操作函数,点亮 LED
extern void led_off(void); //!<硬件无关的 LED 操作函数,熄灭 LED
void led_task(void)
{
	led_on(); //!<点亮 LED
	delay( DELAY_500MS ); //!<延时 500 毫秒, DELAY_500MS 是一个宏
	led_off(); //!<熄灭 LED
	delay( DELAY_500MS ); //!<延时 500 毫秒
}

void main(void)
{
	...
	while(1) 
	{
		...
		led_task(); //!<这是一个阻塞任务,只要它不完成一次灯的闪烁就不会退出
		...
	}
}

这是一个典型的例子,堪称嵌入式学习的“hello world”。这也是一个阻塞型代码,当系统调用 delay()
函数进行延时的时候, CPU 处于一种“死等”的状态,无论你的内核多么强劲,死等都会把你“一句打
回解放前” ——这就是阻塞代码的威力,一夫当关万夫莫开,“阻塞”的含义体现的淋漓尽致。 在操作
系统环境下,情况得到了改观,即便某个任务以阻塞代码的形式编写,且没有使用 OS 提供的带有任务
休眠功能的延时函数 sleep(),当高优先级任务就绪时,系统也会毫不犹豫的打断当前“阻塞”的任务,
保证了整个系统其它任务的流畅运行。如果我们用状态机的非阻塞形式来实现上述功能,则是另外一番
景象。

首先,我们实现一个非阻塞的延时函数:

typedef enum 
{
	fsm_rt_on_going = 0, //!<状态量 on going,表示状态机正在执行
	fsm_rt_cpl = 1, //!<状态量 complete, 表示状态机执行完毕
} fsm_rt_t;

#define RESET_FSM() do { s_tState = START; } while(0)
//! 状态机编写的非阻塞延时函数
fsm_rt_t delay_fsm( uint32_t wTime )//其实就是这个延时函数需要不停地调用(每调用一次计数值就减1),不是只调用一次就一直在延时。
{
	static enum 
	{
		START = 0,
		WAIT_FOR_DELAY,
	} s_tState = START; //!<定义一个状态变量,用以表示状态机当前的状态
	
	static uint32_t s_wDelay; //!<实际用于延时的静态局部变量
	
	switch (s_tState) 
	{
		/* START 状态机的进入“事件”,在状态机复位后首次运行时,运行且仅运行一次,用于初始化
		状态机环境(比如状态机用到的各类变量) */
		case START:
		s_wDelay = wTime; //!<记录要延时的时间长度
		s_tState = WAIT_FOR_DELAY; //!<切换状态
		//break; //!<使用 fall-through 的方法,提高效率
		/* 实际用于延时的状态 */
		case WAIT_FOR_DELAY:
		if (0 == s_wDelay) 
		{
			RESET_FSM(); //!<复位状态机
			return fsm_rt_cpl; //!<说明状态机执行完成
		}
		s_wDelay --; //!<更新计数器
		break;
	}
	
	return fsm_rt_on_going; //!<默认情况下返回状态机正在执行
}

接下来我们在超级循环中实现一个非阻塞的状态机,同样实现 LED 的闪烁功能:

fsm_rt_t led_task_fsm(void)
{
	static enum 
	{
		START = 0,
		LED_ON,
		DELAY_1,
		LED_OFF,
		DELAY_2
	} s_tState = START;
	
	switch (s_tState) 
	{
		case START: //!< START 作为基本格式保留
			s_tState = LED_ON; //!<切换状态
			//break;
		case LED_ON:
			led_on(); //!<点亮 LED
			s_tState = DELAY_1; //!<切换状态
			break;
		case DELAY_1:
		//! 调用延时子状态机
		if (fsm_rt_cpl == delay_fsm( DELAY_500MS )) //太经典了!进入delay_fsm这个函数后立马返回,而不会死等在里面!
		{
			//! 如果延时完成了
			s_tState = LED_OFF; //!<切换状态
		}
		break;
		case LED_OFF:
			led_off(); //!<熄灭 LED
			s_tState = DELAY_2; //!<切换状态
		break;
		case DELAY_2:
		//! 调用延时子状态机
		if (fsm_rt_cpl == delay_fsm( DELAY_500MS ))
		{
			//! 如果延时完成了
			RESET_FSM(); //!<复位状态机
			return fsm_rt_cpl; //!<返回状态机完成
		}
		break;
	}
	return fsm_rt_on_going; //!<默认返回状态机正在执行
}

void main(void)
{
	while(1)
	{
		//! 这是一个非阻塞的任务,无论是否在延时,状态机都会很快释放 CPU。
		led_task_fsm();

	}
}

由于状态机具有非阻塞的特性,因而多任务协同时往往表现出较好的实时性,这是缺乏调度算法的协程
所难以实现的。我们可以简单的认为:相比线程,状态机编写更复杂,但掌控性较强,资源的占用较小;
相比协程,状态机的编写更复杂,但在提高任务实时性方面较为容易。
 

 

总结:仔细看函数的调用过程,不难发现,非阻塞的延时函数需要每次调用(每次调用后计数值减1),led灯的函数也是状态机的结构循环往复调用延时函数。

阻塞延时:就是调用这个延时函数就会一直被阻塞在计数值递减上,做减法操作。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值