2015-02-27 9 views
0

Объекты, которые мы можем использовать в системах Unices для асинхронных предупреждений ввода-вывода, таких как epoll на Linux, kqueue в BSD-системах и Solaris/dev/poll или порты ввода-вывода, позволяют пользователю указывать указатель, который будет ассоциироваться к файловому дескриптору пользователь хочет получать предупреждения ввода-вывода.Epoll, kqueue, указатель, указанный пользователем: как безопасно освободить его в многопоточном режиме?

Обычно в этом указателе пользователь указывает указатель на структуру, которая будет абстрагировать дескриптор файла (например, структуру «Поток» или что-то в этом роде), и пользователь будет выделять новую структуру каждый раз, когда новый файл дескриптор открыт.

E.g. struct stream { int fd; int flags; callback_t on_read_fn; /* ... */ };

Теперь, мой вопрос: как безопасно освободить эту структуру, которую пользователь выделил в многопоточном envinronment?

Я спрашиваю об этом из-за природы epoll/kqueue/etc: Обычно у вас есть поток, который «загружает» из ядра вектор событий, содержащий файловые дескрипторы, которые имеют некоторую готовность ввода/вывода, и указатель пользователя, связанный с этим файловым дескриптором.

Теперь давайте рассмотрим, что у меня есть 2 потока: T1, который загружает эти события и обрабатывает их, например. вызывающий stream->on_read_fn(); и т. д., и T2, который просто запускает код пользователя, пользовательские события и т. д.

Если T2 хочет закрыть дескриптор файла, просто close(stream->fd); и T1 больше не получит никаких предупреждений ввода-вывода для этого fd, поэтому его можно освободить от структуры stream.

Но ЧТО, если поток Т1 уже загрузил тот же самый дескриптор файла в вектор событий, который он обрабатывает прямо сейчас, и он еще не обработал этот дескриптор файла?

Если T1 запланирован ПЕРЕД Т2, все будет ОК, но если T2 запланирован ПЕРЕД Т1, он закроет дескриптор файла и освободит структуру stream, поэтому поток T1, когда он обработает этот файловый дескриптор, будет иметь связанный с пользователем указатель, указывая на уже освобожденную структуру! Конечно, это будет катастрофой.

Я считаю, что T2 будет никогда знать, если поток T1 скачал несколько предупреждений ввода/вывода для конкретного дескриптора файла, ни T2 может предсказать IF T1 будет когда-нибудь скачать некоторые оповещения I/O или нет!

Это очень сложно, и это делает мою голову вращением. Есть предположения? Когда безопасно освобождать указанный пользователем указатель в этом сценарии?

ПРИМЕЧАНИЕ: мой друг предложил удалить дескриптор файла из очереди epoll/kqueue, прежде чем называть close(2). Это правильно, и это то, что я делаю прямо сейчас, но это не решит проблему, потому что T2 может удалить дескриптор файла из очереди epoll/kqueue, но это не гарантирует событие ввода-вывода для этого файла дескриптор не был уже «загружен» из ядра и вскоре будет обработан потоком T1.

ответ

1

У меня была точно такая же проблема, поэтому в новых предложениях ядра Linux кто-то (не помню имя) предложил реализовать статус DISABLED для FD, чтобы вы могли пропустить обработку, если она была освобождена другим потоком ,

Лично я перешел от многопоточных вызовов epool к одному потоку, который epool() на FDs, а затем планировал события для нескольких потоков. Объект внутри внутри - подсчет ссылок и собран позже сборщиком мусора.Работает довольно хорошо, честно и без заметного ухудшения против многопоточного решения epool ...

* EDITED *

Кроме того, я исследовал еще один способ закрыть FD из той же нити, чем ручки epool путем создания std :: set, защищенный мьютексом, и заполняется потребительскими потоками, пока FD должен быть закрыт. Это тоже неплохо.

+0

и другое решение, если вы хотите, чтобы распараллелить epool абсолютно, заключается в разделении очереди FDS по резьбе, в основном назначить другую epool набора для каждого потока, и сохранить ссылку на epool в объект, так что вы знайте, какой набор вам нужно вооружить. –

+0

Спасибо за ваш ответ. Да, я уже пробовал все эти подходы, но мне было интересно, можно ли просто использовать несколько потоков с одним и тем же epoll fd, не используя std :: set или подобные «трюки». Статус DISABLED для fd довольно интересен, но для этого потребуется еще один вызов syscall. –

+0

Я нашел патч, посмотри на это: http://lwn.net/Articles/520022/ кажется просто дополнительным флагом, без syscall. ЕСЛИ вам не нужна совместимость с ядром, это звучит как очень эффективное решение. –

0

Я бы предпочел не использовать одну и ту же структуру данных между двумя потоками.

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

Как только вы закончите обработку события, вам нужно добавить его обратно в epoll/kqueue (как указывает Linux-документ, «переставить» fd).

  • В Linux:

    Добавить в Epoll: epoll_ctl()/EPOLL_CTL_ADD, флаги EPOLLET | EPOLLONESHOT

    Re-рука: epoll_ctl()/EPOLL_CTL_MOD используя те же флаги событий.

  • На BSD/OSX с Kqueue

    Добавить в Kqueue: EV_SET (... EV_ADD | EV_ONESHOT ...);

    Re-arm: EV_SET (... EV_ADD | EV_ONESHOT ...);

  • В Solaris

    просто использовать port_associate(), чтобы добавить и повторно руку.

+0

Это не решает проблему: описанный выше случай может произойти даже при включенном ONESHOT. Поток T2 хочет закрыть и освободить, даже если T1 «загрузил» fd в свой вектор, с включенным ONESHOT. –

+0

Как это происходит. T1 никогда не должен получать уведомление, потому что предыдущее событие (которое обрабатывается в T2) еще не обработано. Вся причина ONESHOT заключается в том, чтобы исправить одновременный доступ к тому же событию. –

+0

OK. Как правило, вы должны закрывать/освобождать только при обработке ошибки сокета read() или write() в коде обработки событий. Чтобы принудительно выполнить событие сокета и ошибку (из потока, отличного от обработки событий), вам необходимо shutdown() на сокете, но не закрыть(). Я не помню 100%, но, возможно, в этом случае также должен быть установлен флаг EPOLLHUP. –