繁体   English   中英

线程安全队列实现(或替代数据结构)

[英]Thread safe queue implementation (or alternative data structure)

我正在尝试实现一个线程安全队列,该队列将保存进入 UART 缓冲区的数据。 队列作为 UART RX-complete-ISR 的一部分写入。 这个队列现在保存了进入 UART RX 通道的数据。 应用程序还需要使用另一个线程来读取队列来处理数据。 但是由于我在没有任何 RTOS 支持的裸机系统上运行所有这些,我想知道这里是否有更好的数据结构可以使用。 因为当我使用队列时,两个线程都需要访问一个公共变量,这可能会导致竞争条件。

我在写这篇文章时意识到这是生产者-消费者问题,我过去解决这个问题的唯一方法是使用互斥锁。 这种方法有替代方法吗?

编辑:

所使用的处理器是基于 ST micro cortex-M0 的处理器。 我研究了 M0 的一些互斥体实现,但找不到任何明确的东西。 这主要是因为 M0 处理器不支持 LDREX 或 STREX 指令,这些指令通常存在于 M3 和 M4 系统中,用于实现互斥体所需的原子操作。

至于系统,代码在启动后直接运行到 main 并且没有OS 功能。 甚至调度程序也是我写的,只是查看一个包含函数指针并调用它们的表。

要求是一个线程从 ISR 写入内存位置以存储通过 UART RX 通道传入的数据,而另一个线程从这些内存位置读取以处理接收到的数据。 所以我最初的想法是我会从 ISR 推送到一个队列并使用应用程序线程从中读取,但由于生产者-消费者设置(使用 ISR)产生的竞争条件,这看起来越来越不可行是生产者,应用程序是消费者)。

您的 M0 是单处理器,因此您可以禁用中断以提供基本排除服务:

int q_put(int c, Q *q) {
   int ps, n, r;
   ps = disable();
   if ((n = q->tail+1) == q->len) {
        n = 0;
   }
   if (n != q->head) {
        q->buf[q->tail] = c;
        q->tail = n;
        r = 0;
   } else {
        r = -1;
   }
   restore(ps);
   return r;
}

int q_get(Q *q) {
   int ps, n, r;
   ps = disable();
   if ((n=q->head) == q->tail) {
       r = -1;
   } else {
       r = q->buf[n] & 0xff;
       q->head = n+1 == q->len ? 0 : n+1;
   }
   restore(ps);
   return r;
}

其中disable禁用返回先前状态的中断,而restore将中断状态设置为其参数。

如果是裸机,那么您将不会有任何互斥锁或更高级的概念,因此您需要自己实现类似的东西。 然而,这是一个常见的场景。

用于此的正常数据类型是环形缓冲区,这是一种队列方式,通过循环数组实现。 你应该把它写成一个单独的模块,但包括两个参数:中断寄存器和用于设置/清除该寄存器的位掩码。 然后让环形缓冲区代码在从环形缓冲区复制到调用者应用程序期间暂时禁用 UART RX 中断。 这将防止竞争条件。

由于 UART 大部分时间相对较慢(< 115.2kbps),因此暂时禁用 RX 中断是无害的,因为您只需要几微秒即可完成复制。 这背后的理论是,ISR 将在每个接收到的数据字节运行一次,但调用者运行与数据完全异步。 因此,不应允许调用者阻塞 ISR 的时间超过计时 2 个数据字节所需的时间,在这种情况下,将出现溢出错误和数据丢失。

这在实践中意味着调用者应该只在比在 1 个数据字节中计时更短的时间内阻止 ISR,因为我们不知道 UART 在当前字节中计时有多远,当时我们禁用了接收中断。 如果中断在字节被计时时被禁用,那应该仍然没问题,因为它应该成为挂起的中断并在您再次启用 RX 中断时触发。 (至少我使用过的所有 UART 硬件都是这样工作的,但请仔细检查您的特定硬件的行为以确保。)

所以这一切都假设您可以比在 UART 上输入 1+8+1 个新位所需的时间更快地进行复制(无奇偶校验 1 停止)。 因此,如果您正在运行例如 115.2kbps,您的代码必须比 1/115200 * (1+8+1) = 86.8us 快。 如果您在那段时间只复制少于 32 位的字,那么 Cortex M 应该可以轻松跟上,假设您运行合理的时钟速度(8-48MHz 之类的)而不是一些低功耗时钟。

您始终需要检查超限和帧错误。 根据 UART 硬件,这些可能是单独的中断或与 RX 相同的中断。 然后以对应用程序有意义的任何方式处理这些错误。 如果发送方和接收方都配置正确并且您没有搞乱时间计算,那么您就不应该有任何此类错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM