2013-02-08 3 views
0

В моей программе на C++ я использую вызов lio_listio для отправки многих (до нескольких сотен) запросов на запись сразу. После этого я делаю некоторые вычисления, и когда я закончил, мне нужно дождаться завершения всех выдающихся запросов, прежде чем я могу отправить следующую партию запросов. Как я могу это сделать?lio_listio: Как дождаться завершения всех запросов?

Прямо сейчас, я просто звоню aio_suspend в цикле, с одним запросом на звонок, но это кажется уродливым. Похоже, я должен использовать аргумент struct sigevent *sevp для lio_listio. Мой текущий догадаться, что я должен сделать что-то вроде этого:

  • В главном потоке, создать мьютекс и зафиксировать его как раз перед вызовом lio_listio.
  • В вызове lio_listio укажите функцию уведомления/обработчик сигнала, который открывает этот мьютекс.

Это должно дать мне желаемое поведение, но будет ли оно надежно работать? Разрешено ли манипулировать мьютексами из контекста обработчика сигнала? Я прочитал, что мьютексы pthread могут обеспечить обнаружение ошибок и потерпеть неудачу, если вы попытаетесь заблокировать их снова из одного потока или разблокировать их из другого потока, но это решение зависит от взаимоблокировки.

Пример кода, с помощью обработчика сигнала:

void notify(int, siginfo_t *info, void *) { 
    pthread_mutex_unlock((pthread_mutex_t *) info->si_value); 
} 

void output() { 
    pthread_mutex_t iomutex = PTHREAD_MUTEX_INITIALIZER; 

    struct sigaction act; 
    memset(&act, 0, sizeof(struct sigaction)); 
    act.sa_sigaction = &notify; 
    act.sa_flags = SA_SIGINFO; 
    sigaction(SIGUSR1, &act, NULL); 

    for (...) { 
     pthread_mutex_lock(&iomutex); 

     // do some calculations here... 

     struct aiocb *cblist[]; 
     int cbno; 
     // set up the aio request list - omitted 

     struct sigevent sev; 
     memset(&sev, 0, sizeof(struct sigevent)); 
     sev.sigev_notify = SIGEV_SIGNAL; 
     sev.sigev_signo = SIGUSR1; 
     sev.sigev_value.sival_ptr = &iomutex; 

     lio_listio(LIO_NOWAIT, cblist, cbno, &sev); 
    } 

    // ensure that the last queued operation completes 
    // before this function returns 
    pthread_mutex_lock(&iomutex); 
    pthread_mutex_unlock(&iomutex); 
} 

Пример кода, с помощью функции уведомления - возможно, менее эффективным, так как дополнительный поток создается:

void output() { 
    pthread_mutex_t iomutex = PTHREAD_MUTEX_INITIALIZER; 

    for (...) { 
     pthread_mutex_lock(&iomutex); 

     // do some calculations here... 

     struct aiocb *cblist[]; 
     int cbno; 
     // set up the aio request list - omitted 

     struct sigevent sev; 
     memset(&sev, 0, sizeof(struct sigevent)); 
     sev.sigev_notify = SIGEV_THREAD; 
     sev_sigev_notify_function = &pthread_mutex_unlock; 
     sev.sigev_value.sival_ptr = &iomutex; 

     lio_listio(LIO_NOWAIT, cblist, cbno, &sev); 
    } 

    // ensure that the last queued operation completes 
    // before this function returns 
    pthread_mutex_lock(&iomutex); 
    pthread_mutex_unlock(&iomutex); 
} 

ответ

2

Если вы установите sigevent аргумент в вызове lio_listio(), вы будете уведомлены о сигнале (или вызове функции), когда все задания в этом конкретном вызове завершатся. Вы все еще должны были бы:

  1. ждать, пока вы не получите столько уведомлений, как вы сделали lio_listio() вызывает, чтобы знать, когда они все сделали.

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

Если вы работаете в Linux, я бы порекомендовал привязать eventfd к вашему sigevent и подождать. Это намного более гибко, поскольку вам не нужно привлекать обработчики сигналов. В BSD (но не в Mac OS) вы можете подождать с помощью kicue, а на Solaris/illumos вы можете использовать порт для получения уведомлений о завершении aiocb.

Here's an example о том, как использовать eventfds на Linux:

Как примечание стороны, я хотел бы использовать осторожность при выдаче заданий с lio_listio. Вам не гарантировано, что он поддерживает более чем 2 jobs, а некоторые системы имеют очень низкие пределы того, сколько вы можете выпускать одновременно. По умолчанию в Mac OS, например, это 16. Этот предел может быть определен как макрос AIO_LISTIO_MAX, но это необязательно. В этом случае вам необходимо вызвать sysconf (_SC_AIO_LISTIO_MAX) (см. docs). Для получения дополнительной информации см. lio_listio documentation.

Вы должны хотя бы проверить условия ошибки в своем вызове lio_listio().

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

Более подходящим примитивом может быть семафор, который высвобождается в обработчике, а затем (после цикла for) был получен столько же раз, сколько вы зацикливали, вызвав lio_listio(). Но я бы по-прежнему рекомендовал eventfd, если это нормально для Linux.

+0

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

+1

Да, в этом случае я бы предположил, что ваше решение с мьютексом отлично работает. Единственное, о чем я бы немного беспокоился, это шаблон освобождения мьютекса внутри обработчика сигнала, возможно, в потоке, который в настоящий момент заблокирован при приобретении блокировки (для списка системных вызовов, защищенных сигналами: http: // pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html#tag_02_04_03). вы используете мьютекс в шаблоне, где традиционно используются семафоры. – Arvid

+0

Действительно, похоже, что функции pthread_mutex_ * не являются безопасными для асинхронного сигнала, а sem_post -. –

Смежные вопросы