Как мне зарегистрировать обновление в обратном вызове из другого потока без использования блокировок? - PullRequest
4 голосов
/ 18 октября 2011

Это один из тех вопросов, которые, похоже, попадают в категорию «наивно очевидных, но, вероятно, неправильных». Конечно, я изо всех сил пытаюсь найти решение, которое работает во всех угловых случаях. Похоже, это проблема, с которой приходится сталкиваться постоянно.

У меня есть «аппаратный» поток и «обрабатывающий» поток.

Аппаратный поток запускается в закрытой двоичной библиотеке, к которой у меня нет доступа.

Поток обработки регистрирует обратный вызов в аппаратном потоке для уведомления о некоторых (редких) событиях, связанных с изменениями состояния в аппаратном обеспечении.

Я хочу иметь возможность уведомить цикл обработки событий в потоке обработки об изменении состояния. Я хотел бы сделать это, не требуя зависимости от внешней библиотеки или непереносимым способом, и не используя блокировки (поскольку я понятия не имею, когда аппаратный поток может захотеть снова уведомить поток обработки).

Итак, в настоящее время я думаю о том, как решить эту проблему, примерно так:

#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_update до следующего вызова processing_function (хотя почти наверняка я могу предположить, что этого никогда не произойдет).

Я пропустил что-то тонкое (или не очень) здесь?

Есть ли лучший способ решить всю эту проблему?

Можно предположить, что обратный вызов всегда вызывается только из одного потока (аппаратного потока).

Ответы [ 3 ]

3 голосов
/ 18 октября 2011

Один из вариантов - использование стандартного самокрутки Unix .Преимущество заключается в том, что конец чтения канала может использоваться с 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 вместо pipe, вы можете использовать eventfd, который в основном является семафором, поддерживаемым в ядре и работающим с read() и write()Системные вызовы.Опять же, преимущество в том, что его можно использовать с циклами событий.

2 голосов
/ 18 октября 2011

Кажется, вы реализовали семафор вручную.

Вместо этого, почему бы не использовать правильный семафор для вашей платформы? Семафоры Posix существуют на платформе ARM.Я не уверен, почему вы не хотите использовать внешние библиотеки, если вы настаиваете на том, чтобы сделать это самостоятельно, то это может помочь .

2 голосов
/ 18 октября 2011

У вас есть доступ к операции сравнения и обмена ? Затем вы можете сделать что-то вроде этого в вашей ветке обработки:

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;
}
...