【问题标题】:How to share data between Tasks/Threads without coupling them?如何在不耦合任务/线程之间共享数据?
【发布时间】:2013-04-03 17:15:52
【问题描述】:

我正在用 C 开发一个相当复杂的微控制器应用程序,我对如何在不同任务/线程之间“链接”我的共享数据而不耦合它们有一些疑问。

到目前为止,我一直使用时间片调度程序来运行我的应用程序,因此不需要数据保护。但我想让应用程序正确,我想让它为以后的多线程操作系统做好准备。

我试图通过使用与我正在使用的实际系统完全不同的系统来简化我的问题。我无法添加图片,因为我是新用户,但我试着解释一下:

我们有 4 个任务/线程:3 个输入线程,它们通过硬件抽象层 (HAL) 从不同的传感器读取一些传感器数据。收集的传感器数据存储在任务域中(即:它们不会是全局的!!)。 现在我们也有 1 个输出任务,我们称之为“调节器”。调节器必须使用(读取)从所有 3 个传感器收集的传感器数据才能生成正确的输出。

问题:Regulator 如何在不与其他任务耦合的情况下读取存储在不同输入任务中的收集数据?

调节器必须只通过引用知道输入任务及其数据(即:没有#includes,没有耦合)。

到目前为止,Regulator 已经有一个指向每个所需传感器数据的指针,并且这个指针是在初始化时设置的。由于数据保护,这在多线程应用程序中不起作用。

我可以为每个传感器值创建一些使用信号量的getSensorValue() 函数,然后使用函数指针将它们链接到 Regulator。但这会占用大量内存!有没有更优雅的方式来做到这一点?我只是在寻找输入。

我希望这一切都是可以理解的:)

【问题讨论】:

  • 您在什么硬件上运行?微控制器系列对可以做的事情有很大的影响。例如,在许多微控制器上没有虚拟内存。在其他控制器中,“线程”实际上并不是线程,而只是看门狗交换了堆栈指针。
  • 目前我在 PSoC5 上运行,我打算移植/转换为 STM32?
  • 那些家庭看起来有真正的线程/虚拟内存表。当您说数据保护将阻止应用程序读取特定于线程的数据时,您的意思是这些“线程”是作为进程而不是线程启动的吗?因为线程可以读取其他线程的内存,除非您通过线程本地关键字(如__thread)明确限制该内存。什么你认为很多内存,3个信号量应该只占用几个字节的内存。或者线程不能因为传感器读取率而被长时间阻塞?
  • 感谢您的回复!我将尝试详细说明:假设我在 Sensor.h 和 Regulator.h 中只定义了 2 个线程。 Sensor 将收集一些数据并将其保存在 Sensor.c 中定义的一些变量中。现在 Regulator.c 必须在不耦合到 sensor.c 的情况下访问这些数据(我的意思是不创建 #include "Sensor.h")。
  • 出于好奇,为什么不能包含 Sensor.h?对于这种情况,您似乎需要一个包含两个标头的主文件,启动所有线程并将它们链接在一起,以便它们可以访问彼此的数据。从您的描述看来,Regulator.c 在不包括 Sensor.c(不推荐)或 Sensor.h 的情况下无法了解 Sensor.c 中的数据?

标签: c microcontroller shared coupling


【解决方案1】:

根据您在问题和 cmets 中的描述,您似乎最担心传感器和调节器之间的接口是内存不足,实现细节最少,并且不知道每个传感器实现的明确细节。

由于您使用 C 语言并且没有某些 C++ 类功能可以通过继承使封装更容易,因此我建议您从每个 Sensor 线程创建一个通用数据包,该数据包传递给 Regulators 而不是传递一个函数指针。一个结构体

struct SensorDataWrap {
    DataType *data;
    LockType *lock;
    ... other attributes such as newData or sensorName ...
};

将允许您将数据传递给监管机构,您可以在读取前锁定。同样,传感器在写入之前需要锁定。如果您将数据更改为双指针DataType **data,您可以使写入命令只需要锁定交换底层指针所需的时间。然后,Regulator 只需要来自每个线程的单个 SensorDataWrap 结构来处理该线程的信息,而不管 Sensor 的实现细节如何。

LockType 可以是信号量,也可以是任何允许单次访问获取的更高级别的锁定对象。任何此类锁的内存占用量应该只有几个字节。此外,您没有在此处复制数据,因此相对于传感器读数,您的内存大小不应该有任何乘法效应。您使用的硬件应该有足够的空间来保存来自您描述的传感器的数据的单个副本,以及足够的闪存空间来容纳信号量或锁定对象。

通信的实现细节现在仅限于锁定、执行操作、解锁,并且不需要复杂的函数指针或 SensorN 特定的标头包含。它应该接近任何线程共享数据程序所需的最小逻辑。该程序还应该可以转移到其他微控制器而无需进行重大更改 - 通信仅受到线程和锁的存在/不存在的限制。

另一个选择是传递一个三重缓冲区对象并进行缓冲区翻转以避免信号量和锁。这种方法需要创建原子整数/布尔支持(如果你有信号量,你很可能已经被编译器公开了)。可以在this blog 上找到使用三重缓冲区进行并发的指南。这种方法会使用更多的活动内存,但可以非常巧妙地避免大多数并发问题。

【讨论】:

  • 好的!这听起来(有点)像消息队列,我有过这样的想法,但得出的结论是(实际上没有任何好的论据)这是一种矫枉过正的做法。虽然这不是你在说的对吗?如果我理解这一点,Regulator 应该引用 SensorDataWrap 的实例(顺便说一下,应该为 Sensor 和 Regulator 声明哪个 typedef),并且在 SensorDataWrap 内部有两种数据和某种保护机制?我喜欢这个主意!双指针部分我不太明白。
  • 没错,我写的实现是一个简化的消息队列,其中监管机构负责在数据被传感器覆盖之前读取数据。消息队列实现也可以正常工作,而且不会过大。双指针是一个小注释。单指针版本要求传感器在将所有读数写入数据时保持锁定。双指针可以让他们写入一个新的“数据”对象,并且只在他们更改指针以寻址新的读数时锁定。不过,这需要内存管理,因此并没有明显更好。
  • 好吧,我实际上认为这比消息队列更好,因为调节器是实时的,因此它不一定会像 Sensor 生成数据那样快地消耗数据。监管机构应始终访问最新数据。但是非常感谢,这正是我正在寻找的输入!
猜你喜欢
  • 1970-01-01
  • 2016-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-19
  • 2011-04-27
  • 1970-01-01
  • 2023-03-25
相关资源
最近更新 更多