IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 如何用生产者消费者问题学RT-Thread的线程间通信 -> 正文阅读

[Java知识库]如何用生产者消费者问题学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);
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-10 11:43:27  更:2022-05-10 11:44:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:00:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码