4

这是似乎属于“天真明显但可能是错误的”类别的问题之一。当然,我正在努力寻找一种适用于所有极端情况的解决方案。看来这一定是一直遇到的问题。

我有一个“硬件”线程和一个“处理”线程。

硬件线程在我无权访问的封闭二进制库中运行。

处理线程向硬件线程注册回调,以通知与硬件状态更改相关的某些(罕见)事件。

我希望能够通知处理线程中的事件循环状态已更改。我想这样做而不需要依赖外部库或以不可移植的方式,也不需要使用锁(因为我不知道硬件线程何时可能想要再次通知处理线程)。

所以,我目前关于如何解决这个问题的想法如下:

#include <signal.h>

// sig_atomic_t is used so update is always in a sane state
static volatile sig_atomic_t update = 0;

// called from hardware thread
int callback_function() {
    update += 1;
}

// called regularly from processing thread
int processing_function() {
    static sig_atomic_t local_update = 0; // The same type as update

    if (local_update != update){
        update_internal_hardware_state(); // We necessarily call once per callback
        local_update += 1;
    }
}

update很明显,如果曾经环绕并且恰好达到local_updatebefore processing_functionis next 调用的值(尽管几乎可以肯定我可以假设这永远不会发生),这将打破。

我在这里错过了一些微妙(或不那么微妙)的东西吗?

有没有更好的方法来解决整个问题?

可以假设回调只从一个线程(硬件线程)调用。

4

3 回答 3

3

一种选择是使用标准的 unix self-pipe trick。好处是管道的读取端可以与事件循环一起使用select()或因此与事件循环epoll()很好地集成,而不必定期轮询原子变量的值。

#include <unistd.h>
#include <errno.h>

int self_pipe[2]; // the read end is non-blocking

// called from hardware thread
int callback_function() {
    write(self_pipe[1], "", 1); // notify by writing a byte
    return 0;
}

// called only when self_pipe[0] is ready for read
int processing_function() {
    for(;;) {
        char buf;
        ssize_t n = read(self_pipe[0], &buf, 1);
        if(n > 0) {
            // callback_function() has been called
        }
        else if(n < 0) {
            if(EAGAIN == errno)
                break;
            // handle an (unlikely) error
        }
        else {
            // handle pipe EOF if necessary
        }
    }
    return 0;
}

如果您使用的是 Linux >= 2.6.22 而不是管道,则可以使用 an eventfd,它基本上是在内核中维护的信号量,并使用read()write()系统调用进行操作。同样,它的好处是它可以与事件循环一起使用。

于 2011-10-18T07:50:18.147 回答
2

您可以访问比较和交换操作吗?然后你可以在你的处理线程中做这样的事情:

int ticks_since_last_check = 0, ticks_ret = 0;
do {
    ticks_since_last_check = ticks_ret;   
    ticks_ret = compare_and_swap(&update, ticks_since_last_check, 0));
} while (ticks_since_last_check != ticks_ret)

这将在处理线程每次检查其值时将滴答计数器重置为 0,因此计数器不会溢出。之后,您只需要运行update_internal_hardware_state函数ticks_since_last_check时间。

或者,您可以使用原子减量:

int ticks_since_last_check = 0;
while (atomic_read(&update) != 0) {
    atomic_decrement(&update);
    ++ticks_since_last_check;
}
于 2011-10-18T02:33:22.403 回答
2

看来您已经手动实现了信号量

相反,为什么不为您的平台使用适当的信号量呢?Posix 信号量存在于 ARM 平台上。我不确定你为什么不想使用外部库,如果你坚持自己做,那么这可能会有所帮助

于 2011-10-18T04:33:15.840 回答