Как говорится в вопросе, у меня есть класс. Код Pesudo выглядит следующим образом:
class MyClass
{
MyClass()
{
epoll_fd = epoll_create(0);
/*start a thread with MyClass::threadFunc() here */
mythread = std::thread( &MyClass::threadFunc, this );
}
void threadFunc()
{
while( true )
{
int numEvents = epoll_wait( epoll_fd, ... );
// some processing here
}
}
~MyClass()
{
mythread.join();
}
std::thread mythread;
int epoll_fd;
}
int main()
{
MyClass inst;
// some processing
return 0;
}
Я хотел бы, чтобы поток начинался в конструкторе и заканчивался деструктором. Однако поток имеет цикл while и ожидает событий. Следовательно, метод join() никогда не вернется, не так ли? Интересно, как лучше всего завершить выполнение потока в этом случае?
Если я использую флаг, позволяющий завершить цикл while, он будет работать только при возврате epoll_wait. Если события нет, программа застревает в методе join(), верно? Итак, в этом случае мне нужно добавить пробуждающий fd в список мониторов epoll и вручную отправить событие самому себе перед вызовом join()?
Вместо псевдокода извлеките минимальный воспроизводимый пример, который гораздо лучше продемонстрирует, в чем проблема. Дело в том, что я полагаю, что ваш вопрос заключается в том, как разблокировать заблокированную ветку epoll_wait()
, верно? Это не имеет ничего общего с классами или функциями-членами.
MyClass:threadFunc должен выполнить какую-то совместную отмену.
Также ваш epoll_wait НЕ должен быть блочной функцией. Вам может понадобиться второй поток для заполнения очереди полученными данными (производитель), и тогда ваш threadFunc станет потребителем.
Интересно, как лучше всего завершить выполнение потока в этом случае?
Скажите потоку выйти, а затем присоединитесь к нему. Механизм зависит от вас.
Вы могли бы:
epoll_wait
сигналом, после добавления соответствующих масок сигналов и логики обработки EINTR
epoll_pwait
и отправьте этот сигналСписок должен включать в себя: создать канал, добавить конец чтения в набор epoll и записать байт в конец записи из сигнального потока... поскольку это, возможно, один из наиболее распространенных способов пробуждения цикла epoll.
eventfd лучше, чем канал, если все, что вам нужно, это разбудить epoll_wait
прерывание epoll_wait
с сигналом после добавления соответствующих масок сигналов и логики для обработки EINTR
Это может быть сложно сделать в любой сложной настройке, такой как многопоточный процесс или в библиотечном коде, где могут быть ограничения на изменение любой обработки сигнала. Вам также, вероятно, потребуется добавить какое-то другое состояние «время выхода», чтобы защититься от ложных пробуждений.
Поскольку в вашей теме используется epoll()
, полезно признать, что вы занимаетесь программированием либо модели актера, либо коммуникационных последовательных процессов, и вы используете epoll()
в качестве reactor
.
Причина, по которой это полезно, заключается в том, что это своего рода указывает на то, как должен работать этот поток (и все остальное, что с ним взаимодействует). По сути, очень хорошая идея придерживаться одной идеи: использовать epoll(), а также включать файловый дескриптор для канала IPC, по которому можно отправлять команды управления потоками. Если единственная команда, которая вам нужна, это «выйти», то получение чего угодно может означать это. Как говорится в ответе от Useless (без сомнения, совершенно неточный дескриптор для участника?), поток завершается, а затем инициатор использует join()
, чтобы дождаться, пока поток фактически завершится.
Хорошая идея — придерживаться одной идеи, потому что когда вы начинаете использовать смешанные парадигмы (например, epoll()
как реактор и signal
s), все может сильно запутаться.
Например (крайний пример): если вы используете epoll(), чтобы определить, когда сокет готов к чтению, а затем используете асинхронный ввод-вывод для фактического выполнения чтения, все может запутаться, особенно если вы начнете выдавать сигналы. слишком.
+1, если только за подход «будь проще». Многопоточную обработку достаточно сложно выполнить правильно даже без посторонних усложнений.
@AndrewHenle Мне очень хорошо удалось сохранить это просто! Иногда я проходил довольно долгий путь. Имея любовь к модели актеров или CSP, мне очень нравится ZeroMQ. Однако я также признаю, что в C++ есть место для таких вещей, как std::queue<shared_ptr<>> и т. д. Итак, я использовал ZeroMQ для арбитража между потоками, используя сообщения для информирования, который теперь имеет право блокировать очередь и читать от него. Звучит сложно, но использование таких конструкций, как std::queue<<shared_ptr<>> рядом с сокетами ZMQ, обеспечивает динамику выполнения последнего со скоростью первого и безопасность памяти!
используйте условную переменную, чтобы уведомить поток о том, что его необходимо остановить.