Объекты, которые мы можем использовать в системах 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.
и другое решение, если вы хотите, чтобы распараллелить epool абсолютно, заключается в разделении очереди FDS по резьбе, в основном назначить другую epool набора для каждого потока, и сохранить ссылку на epool в объект, так что вы знайте, какой набор вам нужно вооружить. –
Спасибо за ваш ответ. Да, я уже пробовал все эти подходы, но мне было интересно, можно ли просто использовать несколько потоков с одним и тем же epoll fd, не используя std :: set или подобные «трюки». Статус DISABLED для fd довольно интересен, но для этого потребуется еще один вызов syscall. –
Я нашел патч, посмотри на это: http://lwn.net/Articles/520022/ кажется просто дополнительным флагом, без syscall. ЕСЛИ вам не нужна совместимость с ядром, это звучит как очень эффективное решение. –