【问题标题】:Lock-free buffer无锁缓冲区
【发布时间】:2013-01-21 10:09:23
【问题描述】:

在我的代码中,我有一个缓冲区,向其中添加数据的代码是:

bool push_string(file_buffer *cb, const char* message, const unsigned short msglen)
{
    unsigned int size = msglen;

    if(cb->head >= (cb->size - size))
    {
        size = cb->size - cb->head - 1;
    }

    if(size < 1) return false;

    char* dest = cb->head += size;

    memcpy(dest, message, size);

    return (size == msglen);
}

由于我从多个中断添加数据(可以相互豁免),我想知道这段代码是否是线程安全的?我将 'cb->head' 标记为 volatile,但如果另一个中断恰好在 'head' 的增加和对 'dest' 的分配之间豁免,事情可能会出错。

如何改进此代码以使其更安全?

编辑:也许我不应该使用“线程安全”这个词,因为没有并行运行的线程,只有中断的可能性。

【问题讨论】:

  • 不...绝对不是线程安全的,因为根本没有同步机制。使无锁算法健壮地实现是困难的——如果你必须问上面是否安全,你应该从互斥体开始。上面的数据结构甚至无法以无锁方式填充,因为您正在写入任意大的消息,而读者无法知道它们何时可以安全使用。
  • 其实我收回了。它不像使用自旋锁那么简单。问题是,如果一个 ISR 获得了自旋锁,然后被另一个 ISR 中断/抢占,那么其他 ISR 将无法获得自旋锁。如果是这样,系统就会死锁。
  • @AlexeyFrunze:这应该有什么帮助?大多数互斥锁实现会在回退到对 CPU 更友好的操作系统级锁之前旋转。简单地旋转用户代码有浪费大量 CPU 周期的风险。至于死锁 - 是否会发生取决于中断是否可以相互抢占或始终运行到完成。
  • @AlexeyFrunze:无锁算法是关于数据本身的旋转——而不是单独的锁。 “旋转不会成为问题” - 您仍然可以获得锁,让步,并让其他线程燃烧它们的整个时间片,并且当互斥体首先旋转时会发生什么?谁想要一种随着锁定频率的增加而呈指数级恶化的锁定方法?
  • 在我的嵌入式系统上,我禁用了中断抢占,(FIQ 除外),正是出于这个原因 - 使缓冲区指针能够在不受其他中断干扰的情况下推入/弹出队列。每个中断处理程序都足够短,足以让我获得足够的性能。 FIQ 可以中断另一个中断,但只能通过启动“普通”软件中断来表示其 SD 卡读/写完成,因此它的缓冲区队列以类似的方式受到保护。 Mutex 在中断处理程序中不可用,即使您有 RTOS。

标签: c thread-safety locking buffer c99


【解决方案1】:

C99 没有线程的概念,因此也没有线程安全的概念。只有 C11 有。 在 C99 中,唯一安全的中断数据类型是 sig_atomic_t,但显然这也没有说明线程。

通常,您在尝试同时访问数据结构时是完全错误的,volatile 完全不能保证您会收到合理的数据。即使在 C11 中,也不能保证任何操作的原子性,因此您可能会遇到指针值的下半部分已经写入但上半部分未写入的情况。这可能会给你一个完全虚假的结果。由于这样的事情可能只会发生百万分之一或在特殊情况下(例如重负载),这可能会导致难以追踪的错误。

不要那样做。

C11 为您提供了处理此类事情的新工具,尤其是原子操作。它尚未完全实现,但许多编译器已经具有可以帮助您的扩展。我已经将其中的一些封装在 P99 宏包中,因此对于某些编译器,您可以从今天开始使用这些功能。

【讨论】:

  • 编码是在嵌入式系统(Cortex M0)上运行的,所以恐怕我不能在我的开发环境中使用 C11,但我会调查一下。
  • 没有理由不应该这样做。特别是关于原子操作的部分应该起作用。 Cortex 不是来自 ARM 家族吗? P99 对该处理器具有原子支持。
  • Cortex M0 在指令集中没有原子操作支持。 Cortex M3 是第一个拥有它的。 P99 使用原子指令实现原子操作,不适用于 Cortex M0。
【解决方案2】:

想想信号中断信号......如果你真的需要:

您可以在 push_string() 中阻止所有相关信号。

另一种依赖于应用程序的可能性可能是将信号处理程序代码移动到主“线程”中(信号处理程序代码只生成唤醒主执行线程的“事件”)。关于您的应用,我没有足够的信息来说明它是否是一个好的选择。

【讨论】:

  • 在我的主线程中,我读出了这个缓冲区并将其写入 SDCard,但是多个中断需要能够填充这个缓冲区。我正在考虑给每个中断一个不同的缓冲区,并将它们组合在 Main() 中,但这将需要大量额外的 RAM,并使事情变得更加复杂。我希望有一种简单的方法可以使这种中断安全。
  • 我完全不确定是否存在,除了在旋转队列索引/指针时禁用中断抢占。正如您所说,为每个中断提供自己的一组缓冲区和一个单独的队列是浪费 RAM,无论如何这些嵌入式作业总是太小:((
猜你喜欢
  • 1970-01-01
  • 2016-07-25
  • 2011-08-14
  • 2019-09-27
  • 2023-03-16
  • 2014-04-11
  • 2014-09-12
  • 2019-06-13
  • 1970-01-01
相关资源
最近更新 更多