lio_listio: как ждать завершения всех запросов?
В моей программе на C++ я использую 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 = ¬ify;
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);
}
1 ответ
Если вы установите аргумент sigevent в вызове lio_listio(), вы получите уведомление (или вызов функции) о завершении всех заданий в этом конкретном вызове. Вам все равно нужно:
дождитесь, пока вы получите столько уведомлений, сколько вы сделали вызовов lio_listio(), чтобы знать, когда они все будут выполнены.
используйте некоторый безопасный механизм для связи от вашего обработчика сигнала к вашему основному потоку, возможно, через глобальную переменную (чтобы быть переносимым).
Если вы используете Linux, я бы порекомендовал вместо этого связать eventfd с вашим sigevent и подождать. Это гораздо более гибко, так как вам не нужно привлекать обработчики сигналов. В BSD (но не в Mac OS) вы можете ждать на aiocbs, используя kqueue, а на solaris/illumos вы можете использовать порт, чтобы получать уведомления о завершении aiocb.
Вот пример того, как использовать eventfds в Linux:
В качестве примечания я бы использовал осторожность при выдаче заданий с помощью lio_listio. Вам не гарантируется, что он поддерживает более двух заданий, а некоторые системы имеют очень низкий предел количества, которое вы можете выдавать за раз. Например, в Mac OS по умолчанию это 16. Этот предел может быть определен как макрос AIO_LISTIO_MAX, но это не обязательно. В этом случае вам нужно вызвать sysconf(_SC_AIO_LISTIO_MAX) (см. Документы). Подробнее см. Документацию lio_listio.
Вы должны как минимум проверить условия ошибок из вашего вызова lio_listio().
Кроме того, ваше решение использовать мьютекс является неоптимальным, поскольку вы будете синхронизировать каждый цикл в цикле for и просто запускать по одному за раз (если это не рекурсивный мьютекс, но в этом случае его состояние может быть повреждено, если ваш сигнал обработчик случается, приземляется в другом потоке).
Более подходящим примитивом может быть семафор, который высвобождается в обработчике, а затем (после цикла for) получается то же количество раз, что и цикл, вызывая lio_listio(). Но я все равно рекомендовал бы eventfd, если все нормально, чтобы быть специфичным для Linux.