如何用生产者消费者问题学RT-Thread的线程间通信

1 信号与任务调度

据实时操作系统RT-Thread的创始人所言,生产者消费者问题是学习实时操作系统绕不过的问题
本文将围绕生产者消费者问题介绍操作系统中的信号量和互斥量,以及死锁和优先级翻转问题。


2 RT-Thread和多线程

RT-Thread是一款完全由国内团队维护时长达到16年的嵌入式实时操作系统,它使用线程调度器(任务调度器)实现单核多任务。

类似于多进程系统中的进程间通信方式,实时操作系统也提供了单核线程间的通信方式。信号量和互斥量就是用于线程间通信的工具。


3 信号量和信号

信号量是一个结构体,它的原型如下。


struct rt_semaphore
{
   struct rt_ipc_object parent;  /* 继承自 ipc_object 类 */
   rt_uint16_t value;            /* 信号量的值 */
};

我们在编程时可以声明各种信号量,当线程对信号量进行获取或者释放操作时,我们称其在获取或者释放信号。

声明一个struct rt_semaphore类型的信号量,每当有线程获取一次这个信号量,计数值value减少1,每当有线程发出一次这个信号时,计数值value增加1。当信号量中的计数值value0时,它就不能被任何线程获取。


在这里插入图片描述
根据信号量计数值为0时不能被获取的特性,我们可以创建一个信号量并初始化为1,让线程获取这个信号,此时这个信号量就为0了,不能再被其他线程获取,直到当前线程释放这个信号量,这样就构成了一个锁。

假设我们规定线程访问内存前必须先取得对应的锁,那么即使多个线程同时申请访问同一块内存,也不怕读写顺序是没有章法的,也就起到了保护内存访问秩序的作用。


4 互斥量

广义上的互斥,指的就是内存只允许被一个线程访问。早期实现互斥,采取的是前面提到的名为“上‘锁’”的机制,即用一个二值信号量标志内存访问权是否被占用。

但这种老方法可能导致线程优先级翻转。RT Thread 的互斥量支持递归调用,并且使用优先级继承协议解决了优先级翻转的问题。


4.1 优先级翻转

就是就绪态的优先级的线程后于相对优先级的线程执行的异常情况。

有优先级为 A、B 和 C 的三个线程,优先级 A> B > C。线程 A,B 处于挂起状态,等待某一事件触发,线程 C 正在运行,此时线程 C 开始使用某一共享资源 M。在使用过程中,线程 A 等待的事件到来,线程 A 转为就绪态,因为它比线程 C 优先级高,所以立即执行。

但是当线程 A 要使用共享资源 M 时,由于其正在被线程 C 使用,因此线程 A 被挂起切换到线程 C 运行。如果此时线程 B 等待的事件到来,则线程 B 转为就绪态。由于线程 B 的优先级比线程 C 高,且线程B没有用到共享资源 M ,因此线程 B 开始运行,直到其运行完毕,线程 C 才开始运行。

只有当线程 C 释放共享资源 M 后,线程 A 才得以执行。在这种情况下,优先级发生了翻转:线程 B 先于线程 A 运行。这样便不能保证高优先级线程的响应时间。

4.2 解决优先级翻转问题

优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。

这样能够防止 C(间接地防止 A)被 B 抢占,如下图所示。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。


5 生产者消费者问题

有关生产者消费者问题,详见【链接


怎样在RT-Thread中复现这一问题的解决呢?这得先抽离出这个问题的本质。我们把它简单描述如下:

  • 该问题包含有生产者、消费者以及一个仓库,共三个对象
  • 生产者在间隔一段时间后向仓库中存放产品,消费者间隔地从仓库中取出产品
  • 生产者和消费者必须打开进仓库的锁才能从仓库存取产品,且二者不能同时去仓库
  • 生产者去仓库的间隔时间较短

这样,我们就可以使用线程、信号量和锁来转述这个问题:

  • 有一个生产者线程和一个消费者线程,共享一个仓库(内存缓冲区)
  • 生产者线程在间隔地增加内存缓冲区的计数,消费者线程间隔地减少缓冲区的计数
  • 两个线程在操作缓冲区计数之前,必须申请取得对应的锁
  • 生产者线程挂起的时间较短

6 在RT-Thread中复现PR问题的解决

这个问题,咱看着代码说


包含线程相关的头文件rtthread.h,因为要开两个线程,得声明两个线程控制块的指针。声明两个信号量,一个用来当锁,另一个用来做仓库中产品的计数。

#include <rtthread.h>
/* 线程控制块的指针 */
static rt_thread_t producer_pid = RT_NULL;
static rt_thread_t consumer_pid = RT_NULL;
/* 信号量 */
struct rt_semaphore sem_lock;
struct rt_semaphore sem_storage;

生产者线程

/**
	The entry for the produceer thread.
	@param parameter, no idea about this parameter. 
	
*/
void producer_thread_entry(void* parameter)
{
	int cnt = 0;
	while(cnt<10){
		rt_sem_take(&sem_lock,RT_WAITING_FOREVER);
		rt_sem_release(&sem_storage);
		rt_kprintf("the producer generate the %dth product.\n",cnt);
		rt_sem_release(&sem_lock);
		
		cnt++;
		rt_thread_mdelay(20);
	}
	rt_kprintf("producer thread exit!\n");
}

这个线程将重复十次同样的操作,首先用

rt_sem_take(&sem_lock,RT_WAITING_FOREVER);

获取仓库的锁。然后用

rt_sem_release(&sem_storage);
rt_kprintf("the producer generate the %dth product.\n",cnt);

释放sem_storage信号,即向仓库中增加产品计数。然后释放锁

rt_sem_release(&sem_lock);

消费者线程

/**
	The entry for the consumer thread
	@param parameter, no means.
*/
void consumer_thread_entry(void* parameter)
{
	int get = 0;
	while(get<10){
		rt_sem_take(&sem_lock,RT_WAITING_FOREVER);
		rt_kprintf("consumer got the %dth product.\n",++get);
		rt_sem_take(&sem_storage,RT_WAITING_FOREVER);
		rt_sem_release(&sem_lock);
		rt_thread_mdelay(50);
	}
	rt_kprintf("consumer thread exit.\n");
}

这个线程将间隔地获取十次sem_storage信号。每次获取前都申请取得锁

rt_sem_take(&sem_lock,RT_WAITING_FOREVER);

然后减少仓库中产品的计数,即获取信号sem_storage

rt_kprintf("consumer got the %dth product.\n",++get);
rt_sem_take(&sem_storage,RT_WAITING_FOREVER);

最后释放锁

rt_sem_release(&sem_lock);

创建两个线程

int producer_consumer(void)
{
	rt_sem_init(&sem_lock,"lock",1,RT_IPC_FLAG_FIFO);
	rt_sem_init(&sem_storage,"storage",10,RT_IPC_FLAG_FIFO);
	
	producer_pid = rt_thread_create("producer",
	producer_thread_entry,RT_NULL,512,24,5);
	if(producer_pid != RT_NULL)
	{
		rt_thread_startup(producer_pid);
	}else{
		rt_kprintf("create thread producer failed");
		return -1;
	}	
	consumer_pid = rt_thread_create("consumer",
	consumer_thread_entry,RT_NULL,512,26,5);
	if(consumer_pid != RT_NULL)
	{
		rt_thread_startup(consumer_pid);
	}else{
		rt_kprintf("create thread cnsumer failed");
		return -1;
	}
	return 0;
}

MSH_CMD_EXPORT(producer_consumer,producer_consumer sample);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值