前后设计模式的不利之处
如果之前您做过无操作系统的嵌入式开发,使用“前台main——后台中断”这种前后台模式开发。
例如,任务1,你要完成接收串口来的1000字节数据,接收完毕数据后,进行校验,校验正确后,把所有数据乘以100,再从串口将数据发送(暂不考虑异常时处理);任务2,你要完成从网口接收1000字节数据,进行校验,校验正确后,把所有数据乘以100,再从网口将数据发送(暂不考虑异常时处理)。
对于上述要求的任务,常规逻辑就是:在串口中断中,将接收到的数据放入数据缓冲区中,当接收完毕1000字节后,置串口数据接收完毕标志,程序代码main中的while(1)循环中查询串口接收标志有效,怎进行数据的校验和乘以100的处理;同理,网络接收数据也是这样处理,中断中接收数据、置标志,主循环中数据处理。C语言伪代码如下:
//串口中断处理函数 void uart_irq(void){ //从串口寄存器中读数据,并放在uart_data[]缓冲区中 uart_data[uart_data_len] = uart_rev_register; uart_data_len ++; //接收到UART_REV_LEN(当前要求为1000字节),则置标志有效 if(uart_data_len >= UART_REV_LEN){ uart_rev_flag = TRUE; uart_data_len = 0; } } //网口中断处理函数 void net_irq(void){ //从网口寄存器中读取数据,并放在net_data缓冲区中 for(temp_i = 0; temp_i < net_len_reg; temp_i ++){ net_data[net_data_len] = net_data_register; net_data_len ++; } //则置标志有效 net_rev_flag = TRUE; uart_data_len = 0; } void main(void){ while(1){ //串口接收标志有效 if(uart_rev_flag == TRUE){ //置标志为无效,用于下次数据接受 uart_rev_flag == FALSE; //调用串口数据处理函数 uart_data_process_func(); } //网口接收标志有效 if(net_rev_flag == TRUE){ //置标志为无效,用于下次数据接受 net_rev_flag == FALSE; //调用网口数据处理函数 net_data_process_func(); } } }
如果你的项目要求20个任务,那你的main会很庞大,但也能做;如果,部分任务之前有任务的紧急程度不同,那用这种“前后模式”设计程序,非常考研设计的任务规划能力,再加上后续用户不断的新增新的任务,那经过若干次修改后,程序简直不忍直视。
忠告:
不要小看了用户修改,我有个项目,历经5年时间,修改了无数的版本;要记住一句忠告“用户虐我千百遍,我待用户如初恋”,用户永远是上帝。
让LED和数码管尽情的跳动吧
必备工具:请将NXEZ瑞士军刀开发板,安装到树莓派上,如下所示
如此复杂的任务,简单解决
设计任务:任务1,LED灯按照流水灯方式进行点亮;任务2,数码管显示程序运行的时间,单位:秒。任务和简单吧,你可以想想用前后台模式怎么设计,是不是要开个定时器,在主循环中一会变led的状态,一会变数码管的状态,好费经吧。
那让我们借助操作系统的多任务这个工具吧;看吧,是不是很简单:)。
from multiprocessing import Process import time import os from datetime import datetime from sakshat import SAKSHAT from sakspins import SAKSPins as PINS #流水灯每个灯点亮时间 WATERLIGHT_DELAY = 0.1 #数码管刷新显示数字的延时 DIGSEC_DELAY = 0.7 #Declare the SAKS Board SAKS = SAKSHAT() #流水灯任务定义 def waterlight(task_name, delay_ts): print(task_name + "任务启动") try: while True: for i in range(0,8): SAKS.ledrow.on_for_index(i) time.sleep(delay_ts) SAKS.ledrow.off_for_index(i) except KeyboardInterrupt: print(task_name + "任务被终止") #数码管显示秒值 def digital_second(task_name, delay_ts): print(task_name + "任务启动") #记录开始时间 start_time = datetime.utcnow() try: while True: end_time = datetime.utcnow() c = end_time - start_time #print c.seconds #print c.microseconds SAKS.digital_display.show(("%02d.%02d" % (c.seconds, c.microseconds))) time.sleep(delay_ts) except KeyboardInterrupt: print(task_name + "任务被终止") if __name__ == "__main__": try: #创建led和数码管显示闪烁任务 led_flash = Process(target=waterlight, args=("流水灯", WATERLIGHT_DELAY)) digsec = Process(target=digital_second, args=("数码管显示", DIGSEC_DELAY)) # 启动任务 led_flash.start() digsec.start() #等待启动的进程执行结束 led_flash.join() digsecjoin() except KeyboardInterrupt: print("任务被终止了")
为什么要使用multiprocessing模块
python提供了os.fork创建进程,提供了thread模块创建线程,这不是足够了吗?
这是不够的,os.fork无法在windows实现,不具有可移植性;线程虽然可以,但由于python的GIL(Global Interpreter Lock,全局解释器锁)的存在,同一时刻python只能运行一个线程,没法将多线程分配到多核上运行。
这就是引入了multiprocessing模块的原因,解决了windows移植到问题和如何利用多核的问题。
如何使用multiprocessing模块呢?就3步。
1.定义自己的任务,
#流水灯任务定义 def waterlight(task_name, delay_ts): print(task_name + "任务启动") try: while True: for i in range(0,8): SAKS.ledrow.on_for_index(i) time.sleep(delay_ts) SAKS.ledrow.off_for_index(i) except KeyboardInterrupt: print(task_name + "任务被终止")
2.将任务交给multiprocess模块,
#创建led闪烁任务
led_flash = Process(target=waterlight, args=("流水灯", WATERLIGHT_DELAY))
1.启动该任务。
# 启动任务
led_flash.start()
看吧,就3步,是不是很简单。
进程的状态
从大的方面来说,进程在运行过程中有3种状态:就绪态、运行态、阻塞态。
(1)就绪态。
就绪态:进程具备运行条件了,可以被cpu执行了;但还在就绪队列中排着队。
例如,我们led灯例程中有两个地方是就就绪态的。
1.进程名.start()执行后,进程进入就绪态了。
2.time.sleep()之后,点亮灯(或熄灭灯)之前,进程进入就绪态了。
(2)运行态。
运行态:进程就是在cpu上运行了。
例如,执行把运行灯操作语句,把灯点亮(或者熄灭)时,这是就是运行态。
(3)阻塞态。
阻塞态:进程在等待某个事件发生的过程中,就是阻塞态;此时,一直在阻塞队列中等待着。
例如,执行time.sleep语句,进行延时时。
多任务好吧,你把做的事情告送操作系统,操作系统帮你进行管理、调度。
对比下,multiprocessing模块和thread模块的效率
下属代码及结论,摘抄自“莫烦python”, 链接:https://morvanzhou.github.io/tutorials/python-basic/multiprocessing/4-comparison/
结论:发现多核/多进程最快,说明在同时间运行了多个任务。 而多线程的运行时间居然比什么都不做的程序还要慢一点,说明多线程还是有一定的短板的。
更多关于multiprocessing的介绍运行下面代码
help("multiprocessing")
注意
本课程不是python语言语法学习课程,因为现在网上有大量优质的python语言语法学习课程,同学们可以自行选择免费的或收费的python语法课程,学习python本身。
本课程定位于:如何使用python理解树莓派、理解操作系统。放心由于python语言的通俗易懂,加上本课程都是最基本的python,及时没有基础,也可以快速理解,记住,咱们的课程是可以边学边练、随意修改、还有恢复机制,放心折腾吧。
置身事外看编程语言
咱们不探究python语言本身,那咱们探究下何为编程语言。(下面是个人理解,咱们可以敞开讨论)
编程语言的目的就是——把我们要做成的事情,翻译成计算机能理解的方式,告送计算机,由计算机帮我们实现。那咱们看看我总结的编程语言三大组成部分:
(1)变量。就是咱们要处理的信息,例如:温度数据、车的速度,楼的高度。变量可以继续划分,但划分的目的就是存储数据的大小,例如8位(2的8次方)变量可以存储0~255数字量,32位数据(2的32次方)存储0~4294967295。还有16位,64位、或者像python中不限制大小的变量。
(2)程序结构。就是咱们处理信息所使用的流程。有顺序结构、条件结构、循环结构。顺序结构很容易理解,就是依次处理数据,例如,我们依次进行,获取温度数据、获取汽车速度;条件结构,就是需要对信息进行判断,然后再处理,例如,获取完毕汽车速度后,判断汽车超速,就进行刹车;循环结构,就是不断地重复相同的事情,例如,我们每隔1s采集一次温度,就是不断循环的进行采集温度、睡眠1s、再采集温度、睡眠1s…。
(3)系统和第三方提供的库。其实有了变量和程序结构,就可以完成咱们要的工作,但是如果事必躬亲的话,估计咱们还处在原始社会。后人进步总是站在前人的肩膀上继续攀登。所以,前人已经他们已完成的工作,打包好,前人已完成的功能,我们理解后直接用,我们专注于自己新的工作。系统和第三方库,就是我们可以使用的现成工具,我们合理利用这些工具,重新组合完成我们新的需求,如果您做的好,您可以将您的工作封装成第三方库,留给后人使用。
这是我理解的编程语言的框架,您可以先按照我的方式看语言,或者自己建立自己语言框架。
课程 bilibili 视频地址:https://www.bilibili.com/video/av71878718/?p=10
课程 gitee 地址:https://gitee.com/shirf_taste_raspi/shirf_serial_share