У меня есть очереди без блокировки, написанные на C в виде связанного списка, который содержит запросы из нескольких потоков, размещенных и обработанных в одном потоке. После нескольких часов стресса я получаю указатель на следующий запрос последнего запроса, который создает бесконечный цикл и блокирует поток обработки.
Приложение работает (и не работает) как в Linux, так и в Windows. Я отлаживаю в Windows, где мои COMPARE_EXCHANGE_PTR
отображаются на InterlockedCompareExchangePointer .
Это код, который отправляет запрос в начало списка и вызывается из нескольких потоков:
void push_request(struct request * volatile * root, struct request * request)
{
assert(request);
do {
request->next = *root;
} while(COMPARE_EXCHANGE_PTR(root, request, request->next) != request->next);
}
Это код, который получает запрос из конца списка и вызывается только одним потоком, который их обрабатывает:
struct request * pop_request(struct request * volatile * root)
{
struct request * volatile * p;
struct request * request;
do {
p = root;
while(*p && (*p)->next) p = &(*p)->next; // <- loops here
request = *p;
} while(COMPARE_EXCHANGE_PTR(p, NULL, request) != request);
assert(request->next == NULL);
return request;
}
Обратите внимание, что я не использую хвостовой указатель, потому что я хотел избежать усложнения необходимости иметь дело с хвостовым указателем в push_request
. Однако я подозреваю, что проблема может заключаться в том, как я нахожу конец списка.
Есть несколько мест, которые помещают запрос в очередь, но в целом они выглядят так:
// device->requests is defined as struct request * volatile requests;
struct request * request = malloc(sizeof(struct request));
if(request) {
// fill out request fields
push_request(&device->requests, request);
sem_post(device->request_sem);
}
Код, который обрабатывает запрос, выполняет не только это, но по сути делает это в цикле:
if(sem_wait_timeout(device->request_sem, timeout) == sem_success) {
struct request * request = pop_request(&device->requests);
// handle request
free(request);
}
Я также только что добавил функцию, которая проверяет список на наличие дубликатов до и после каждой операции, но я боюсь, что эта проверка изменит время, так что я никогда не столкнусь с точкой, где она потерпит неудачу. (Я жду, пока он сломается, когда я пишу это.)
Когда я нарушаю программу зависания, нить обработчика зацикливается на pop_request
в отмеченной позиции. У меня есть действительный список одного или нескольких запросов, и следующий указатель последнего указывает на себя. Очереди запросов обычно короткие, я никогда не видел больше 10, и только 1 и 3 два раза я мог посмотреть на эту ошибку в отладчике.
Я обдумал это столько, сколько мог, и пришел к выводу, что я никогда не смогу закончить циклом в своем списке, если я не нажму один и тот же запрос дважды. Я совершенно уверен, что этого никогда не произойдет. Я также довольно уверен (хотя и не полностью), что это не проблема ABA .
Я знаю, что мог бы выдать более одного запроса одновременно, но я считаю, что это не имеет значения, и я никогда не видел, чтобы это происходило. (Я тоже это исправлю)
Я долго думал о том, как я могу нарушить функцию, но я не вижу способа закончить с петлей.
Итак, вопрос: Может кто-нибудь увидеть способ, как это может сломаться? Может кто-то доказать, что это не может?
В конце концов, я решу это (возможно, используя хвостовой указатель или другое решение - блокировка может быть проблемой, потому что потоки, которые публикуются, не должны быть заблокированы, хотя у меня есть блокировка RW под рукой), но я бы хотел убедитесь, что изменение списка действительно решает мою проблему (в отличие от того, что делает его просто менее вероятным из-за разного времени).