4-1RT-Thread信号量
在实时系统中,一项工作往往需要多个线程共同完成。而线程对CPU的使用权由其优先级来确定。如果线程的功能是独立的,如控制LED灯周期性闪烁,那么我们只需要关注线程具体功能的实现即可。但在线程之间需要配合完成某些功能时,则需要严格的逻辑控制。如图所示。
Thread 1和thread 2都可以访问共享内存块,并且thread1将数据写入共享内存块后,thread 2才可以访问。如果对共享内存的访问不是排他性的,那么各个线程之间可能同时访问它,这将引起数据的一致性问题。例如在thread 2试图读取数据之前,thread1还未完成数据的写入,那么thread 2读取的数据将是不完整的,会造成读取数据的错乱。为了防止出现数据的差错,两个线程访问的动作必须是互斥进行的。应该是在一个线程对共享内存块操作完成后,才允许另一个线程去操作。这样线程一与线程二才能正常配合,使工作正确的执行。
多个线程操作或访问同一个区域或代码,这块代码我们就称作临界区。上述例子中的共享内存块就是临界区。而线程的互斥是指对于临界区资源访问的排他性。当多个线程都要使用临界区资源时,任何时刻最多只允许一个线程去使用,其他要使用该资源的线程则需要等待,直到占用资源者释放该资源。线程的同步方式有很多,其核心思想都是在访问临界区时,只允许一个或一类线程运行。
如何对临界区进行保护?我们可以使用禁止调度或关闭中断的方式进行。禁止调度是调度器上锁,上锁后不再切换到其他线程,仅响应中断。在临界操作完成之后,再将调度器调用功能打开。关闭中断与禁止调度类似,关闭中断后进行临界操作,临界操作完成后再将中断打开。使用互斥特性进行临界区保护的方式主要有信号量和互斥量。
以停车场为例来理解信号量的概念。当停车场空的时候,门禁会让外面的车陆续进入停车场,获得停车位。当停车场车位满的时候,门禁将禁止外面的车进入停车场,车辆在外排队等候。当停车场内有车离开时,门禁发现有空的车位让出,允许外面的车进入停车场,待空车位填满后,又禁止外部车辆进入。在此例中,门禁相当于信号量。停车场相当于公共资源,即临界区。空车位的个数就是信号量的值,此值是非负数,并且是动态变化的。车辆相当于线程。车辆通过获得门禁的允许取得停车位,就类似于线程通过获得信号量访问公共资源。
每个信号量对象都有一个信号量的值和一个线程等待队列。信号量的值对应了信号量对象的实例数目。例如信号量的值为五则表示共有五个信号量实例可以被使用。当信号量的实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例。
常用的信号量类型有二值信号量和计数型信号量。其中二值信号量实例初始化值为一,代表系统默认有一个资源可用。当二值信号量的值为零时,代表临界区正被访问,其他要访问此临界区的线程将被挂起。在该信号量上当某个时刻中断或者线程释放了信号量,那么挂起在等待队列中的线程将获得信号量并恢复为就绪状态。
计数型信号量与二值信号量差不多一样用于资源保护。计数型信号量则允许多个线程获取信号量的访问共享资源,但会限制线程的最大访问数目。访问的线程达到信号量可支持的最大数目时会阻塞其他试图获得该信号量的线程,直到有线程释放了信号量。如图所示,本例中计数型信号量的值为三,可以允许三个线程同时访问资源。当有第四个线程需要访问资源时,此时已没有可用信号量,因此线程四会被挂起。当其中线程一释放了信号量之后,线程四才可以获取信号量访问公共资源。