| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Программирование под Unix/Linux > [pipe] возможность прервать блокирующую операцию |
| Автор: cupper 15.3.2012, 22:33 | ||||
| Пытаюсь реализовать корректное завершение потока заблокированного на операции с pipe'ом. Первый вариант работа в лоб
заблокируется на операции open до тех пор пока другой процесс не откроет pipe на запись. Прервать эту операцию на вряд ли получится. Я не нашел способа. Второй вариант, используем неблокирующий режим
Пояснение 1: после того как мы "открыли" pipe переводим его в блокирующий режим иначе каждая наша операция будет сразу возвращаться управление. Пояснение 2: используем select, но мы не можем использовать его для операции read так как она возвращает 0 на еще не открытом pipe, поэтому открываем pipe в оба режима, и блокируемся на операции write. Я было надеялся что операция write прервется если мы сделаем close(), но она не прерывается. Так же не появляется исключительная ситуация (для которой я попробовал установить 4 параметр select). Долго и усердно пытался найти как прервать select. Не нашел. Вариант использования pselect плохой, так как я не могу послать сигнал в поток он шлется в процесс. Единственным вариантом пока вижу прерывать поток открытием pipe со второй стороны, ну и например записью туда какого нибудь значение по которому прерываемый поток узнает что это команда exit. Но и этот вариант мне не кажется хорошим. Излазил весь интернет, все что нашел сведено в коде выше. PS. хммм, обнаружил такую функцию pthread_kill... неужели в лине можно послать сигнал конкретному потоку ? |
| Автор: cupper 16.3.2012, 20:52 | ||||
Ну это по сути ненужное усложнение способа открыть пайп и записать туда что нибудь.
И что изменится ? Повисну в нем так же как и в select. Уточните пожалуйста, что вы имели в виду под этим, я может вас не понял. |
| Автор: Fynivx 16.3.2012, 22:49 |
| А нельзя использовать стандартные средства плюсов? condition_variable, например? Там есть метод, который может ожидать сигнала определенное время. А во втором процессе - посылать сигнал после открытия. Потоки уже стандартизированы в C++... |
| Автор: cupper 16.3.2012, 23:12 | ||
в винде я так и сделал, там есть Event и есть WaitForMultipleObject. В linux аналога нету. Есть select который умеет проверять что следующая операция (чтения, записи) не будет блокирующая. Есть pselect который еще умеет ждать сигнал (но сигналы то шлются все тредам, хотя есть pthread_kill но его работа для меня пока не очень ясна, да и сигнал это не средство синхронизации). Использовать TimeOut вообще плохая затея. Но я не понял как вы предлагаете скрестить condition_variable и блокирующую open или write ? Это как раз то что нужно, вот только я не знаю возможно ли такое |
| Автор: Fynivx 17.3.2012, 00:19 | ||
Блокирующий - не знаю. А с неблокирующим возможно. По крайней мере, я представляю примерно так: Первый процесс создает и открывает очередь в неблокирующем режиме, после чего вызывает wait с таймаутом. Это для того, чтобы, если второй процесс так и не откроет соединение, первый не завис в бесконечном ожидании. Если сообщение было получено, начинается обмен. Если нет - очередь закрывается. А второй, опять же, открывает очередь, и посылает первому сообщение об открытии через notify. Как-то так. notify_one посылает сигнал одному процессу, notify_all - всем ожидающим. Вообще можно организовать очередь любого типа нативными средствами. По крайней мере, я бы так сделал. И никакой привязки к операционке не будет. Хм... Или у Вас привязка к C а не к C++? А можете описать задачу взаимодействия процессов в целом? Может, что проще придумается и без хэдеров windows и pthreads. |
| Автор: svlary 17.3.2012, 06:21 | ||
А зачем ее прерывать ? Смысл pipe как раз и заключается в том, что СЕРВЕР должен повиснуть до тех пор, пока КЛИЕНТ не положит свое сообщение в pipe. Главная мысль : pipe должны использовать два (!) процесса. И один из них должен постоянно висеть на чтении. Проще говоря - ждать прихода сообщения. И это абсолютно нормальная логика клиент/серверных технологий. Зачем ее ломать изобретая деревянные велосипеды на треугольных колесах - я не понимаю. Если же Вы хотите что бы и клиент и сервер запускались из одного исполняемого модуля, то можно сделать так :
Для ознакомления с классикой программирования под Unix/Linux рекомендую хотя бы : Марк Дж Рочкинд. "Программирование для UNIX". |
| Автор: Fynivx 17.3.2012, 07:04 |
Бывают такие ситуации. Вот у меня, например, сейчас в одном проекте наблюдается следующая картина: куча неконтролируемых сверху процессов работают с одной областью памяти, причем никто из них не знает, кто когда завершится или прервется. Нетрудно представить ситуацию, когда пара таких процессов, вместо использования общей памяти, общаются между собой пайпами. И тогда нужно предусмотреть ситуацию, когда один из них внезапно сломается. Но я, честно говоря, не могу представить ситуацию, где их использование выгоднее) Добавлено через 3 минуты и 31 секунду Даже для двух совершенно разных модулей, написанных на разных языках, можно сообразить шаред-мемори через dll'ку) |
| Автор: rsm 17.3.2012, 09:50 |
Как один из самых элементарных вариантов: а) создаём маску сигналов, в которой заблокировано всё; б) назначаем эту маску процессу; в) удаляем из маски какой-то сигнал, к примеру SIGUSR1; г) висим на pselect; д) когда надо, сами себе оправляем заранее определённый сигнал (SIGUSR1); е) pselect завершается по прерванному системному вызову; Вариант другой, более годный: вынести обработку сигналов в отдельный поток. Делать такое необходимо, если в программе используется таймер, т.к. все блокируемые системные вызовы будут прерываться по каждому тику таймера. Оба варианта хорошо описаны у http://www.ozon.ru/context/detail/id/3406745/. |
| Автор: xvr 17.3.2012, 10:06 | ||||
Ну чуть чуть отличается. Вы используете свой конец пайпа, чужой может быть эксклюзивно занят клиентом, и вы его не сможете открыть
У него больше возможностей по вариантам ожидания. Возможно какая то из них может сработать например на банальное закрытие handle'а |
| Автор: cupper 17.3.2012, 10:37 | ||||||
мысли глубже. А как вы сервер выключать будете ? kill -9 ? или pthread_abort ?
а второго нету, pipe открывает из ОС.
Вы выше мои изливания душевных мук на счет сигналов и синхронизации читали ? Ну по сути как не крутись есть только два способа * pselect + signal * открыть pipe для того что бы завершить поток ожидающий его. Второй явно безопаснее и проще. |
| Автор: rsm 17.3.2012, 16:19 | ||
Читал. Не проникся, т.к. не смог обнаружить признаков понимания работы сигналов и их воздействия на системные вызовы, потоки и процессы. Потому и посоветовал заглянуть в книжку. |
| Автор: cupper 17.3.2012, 18:41 | ||||
убедили. Да я действительно не понимаю как работают сигнала, и это то из за чего я не хотел их использовать. Загляну в книжку, проникнусь. |
| Автор: rsm 17.3.2012, 21:17 |
| Накатал на досуге пример (в аттаче). Для его полного понимания потребуется знать матчасть, ибо объяснять всё "от Адама" не хватит никакого форума P.S. Извиняюсь за архив в zip - как выяснилось, родимый tgz не поддерживается для аплоада |
| Автор: svlary 18.3.2012, 05:28 | ||||||
О том, что использование COMMON переменных - весьма опасная практика, догадались еще програмисты на fortran-е 40 лет назад... [/quote] нужно предусмотреть ситуацию, когда один из них внезапно сломается.[/quote] Специально для этого придуманы сигналы SIGPIPE и SIGCHILD. Надо просто написать обработчик соответствующих сигналов. Добавлено через 10 минут и 48 секунд Для этого в ОС Linux существует стандартная методика. Например, сервер MySQL гасится командой :
Разработчики любых других серверов ОБЯЗАНЫ предоставить системе аналогичные средства.
Это - как ?! |
| Автор: cupper 18.3.2012, 15:25 | ||||
echo test > my.pipe |
| Автор: cupper 19.3.2012, 09:05 | ||
А теперь я вам скажу почему это неверно, и что я заблуждался. Открывая pipe со второй стороны что бы прервать первые мы рискуем заблокировать на этом. Если вторая сторона уже не пробует открыть pipe. Например из за какой то системной ошибки и thread прекратил работать, а мы пытаясь открыть pipe, то мы зависним в основном потоке. И даже еще хуже, если наш тред работает, и весит на fopen. Наглядный эксперимент показал что мы спокойно в этот момент может удалить сам файл pipe физически. И тогда у нас и thread и поток вызывающий stop зависнут, и сделать уже ничего нельзя будет. Остается один только выход pselect + signal. ... О как долго до меня доходит все это... о как долго... |
| Автор: svlary 19.3.2012, 12:16 | ||
Если я правильно понял, то
Или Вы имели в виду, что где-то в теле программы echo присутствует системный вызов open(..) ? Но этот вызов есть во всех (!) прикладных программах. А ОПЕРАЦИОНКА (ядро Linux) работает с файлами совсем по другому... Понимание того факта, что средство pipe, ПРЕДОСТАВЛЯЕМОЕ операционной системой (ядром Linux) предназначено именно для взаимодействия двух прикладных программ (сервер и клиент) и дает ключ к пониманию того, как правильно использовать данную возможность ОС.
Добавлено через 13 минут и 49 секунд
Не понял... Вы хотите сказать, что операция open(...), обнаружив, что файла НЕТ, вовсе не вернет -1 и не установит errno == ENOENT а просто повиснет ?! |
| Автор: cupper 19.3.2012, 19:59 | ||||||
простите но вы кэп про всякое там правильное взаимодействие с pipe комментировать не буду.
Уху |
| Автор: cupper 19.3.2012, 23:26 |
| Вкуриваю тему сигналов. Споткнулся о sigprocmask и pthread_sigmask. Ну вернее в начале я всттетил кучу примеров именно с sigprocmask. Далее смлучайным образов нашел pthread_sigmask, еще немного полистав интернет таки понял в чем разница. Но не нашел аналогичного для sigaction... ну вот не хочу я обработчик ставить для всех, хочу только для текущего треда... что делать ? |
| Автор: svlary 20.3.2012, 07:01 |
Может быть и так...
Не смею больше Вам мешать... |
| Автор: rsm 20.3.2012, 17:25 | ||||
Сигналы отправляются процессу, а не потоку. Можно выделить отдельный поток, который будет обрабатывать очередь поступающих сигналов - однако это не изменит того, что они приходят процессу.
От этого помогает мультиплексирование (даже если оно как таковое не требуется), т.к. переданные дескрипторы проверяются на готовность к чтению. Если они не готовы, можно выйти либо по таймауту, либо по сигналу. Предлагаю всё-таки потыкать в приаттаченый выше пример. |
| Автор: cupper 21.3.2012, 10:21 | ||||||
я как то не правильно понимаю либо вас либо описание выше ?
Забыл :( Посмотрел. Удивлен. poll умеет реально определять были ли положены в pipe данные, когда select меня просто по бороде пускал и пролетал мимо. Тогда если скрестить pthread_kill и ppoll то вроде бы получиться то что надо. Спасибо. |
| Автор: rsm 21.3.2012, 16:07 |
Есть один нюанс в терминологии: в GNU/Linux потоки - это по факту процессы |
| Автор: cupper 21.3.2012, 23:08 | ||||
ну я так и знал что вы это скажите И еще, у меня вопрос по вашему примеру
Где опечатка ? В комментарии или в SIG_SETMASK ? По сути этот код ничего не делает. И тут я подхожу к своему следующему вопросу. Я понимаю что делает sigaction и я понимаю что делает sigprocmask. И так, первая функция регистрирует НАШ обработчик для заданного сигнала. Вторая функция напротив блокирует получение процессом сигнала. Далее функции типа ppoll, pselect снимают блокировку сигналов которые вы укажите им в параметрах. Но, они гарантируют, что сразу после получения сего сигнала, его настройки будут возвращены в то состояние, которые было перед вызовом. В нашем случае состояние БЛОКИРОВАНИЯ сигнала. Таким образом, нет смысла использовать их вместе. Но, стоит понять какую из них НУЖНО использовать. Итак, если мы используем sigprocmask то мы заблокируем сигнал, далее вызовем ppoll. Но как мы выйдя из ppoll сможем определить почему мы вышли ? из за сигнала или из за того что поступили данные ? Спецификаци ppoll говорит нам что errno будет установлено в EINTR если мы вышли из за сигнала. Гип гип ура. Далее, если мы используем sigaction, то по выходу из ppoll мы собстно можем узнать это по какой то внешней переменной которую мы установили в обработчике сигнала. И опять таки гип гип ура У вас в примере, реализовано сразу оба способа (что ни есть гуд) Если я где то ошибся поправьте меня. -- истина где то рядом |
| Автор: rsm 22.3.2012, 03:20 | ||
Разные термины возникли не зря - не смотря на то, что потоки по факту процессы, сигналы в потоках и процессах обрабатываются по-разному. Не стоит об этом забывать, иначе однажды можно наступить на неприятные грабли Я не понял, в чём состоит вопрос |
| Автор: cupper 22.3.2012, 09:04 | ||||||
пример граблей ? первый на счет
Сам вопрос в посте выше.
пример ? Ну или хотя бы где это описано. На счет sigprocmask и sigaction пока молчу, нужно еще поэксперементировать. |
| Автор: rsm 22.3.2012, 16:26 |
У каждого потока своя маска сигналов, однако диспозиция сигналов - одна на всех. Если один поток установил диспозицию сигнала на игнорирование, а другой поток - на обработчик, то по приходу сигнала будет использована диспозиция, установленная последней. Как следствие - грабли, которые будет сложно отловить, т.к. визуально все выглядит корректно. Всё равно не понимаю, в чём вопрос? У http://www.ozon.ru/context/detail/id/3406745/ этому посвящена целая глава 10.5, страница 373. |
| Автор: cupper 22.3.2012, 18:33 | ||||
чего ? можете пояснить ?
кажется с совсем запутался. Я точно помню что я где то читал что сей параметр (или может просто похожий) заставляет сбросить state для заданных сигналов в дефолтное значение.
эх, будем вкуривать. Черт побери эти линуксы :( |
| Автор: rsm 22.3.2012, 20:03 | ||||
"Диспозиция" можно без потери смысла заменить на "http://ru.wikipedia.org/wiki/Callback_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)" - не знаю, откуда переводчик книги Стивенса выкопал это необычайное слово (диспозиция)
Пример, полагаю, понятен? А теперь представим, что будет, если каждый вызов функции signal выполняется в отдельном потоке. Какая именно диспозиция будет назначена для сигнала USR1: игнорирование, действие по-умолчанию или callback? Если не синхронизировать потоки принудительно, получится игра в рулетку - нельзя предсказать, что будет диспозицией сигнала, т.к. достоверно неизвестно, какой поток выполнит вызов функции signal последним. Это и есть те грабли, про которые я упоминал выше, говоря об особенностях обработки сигналов в потоках. P.S. В современных программах никогда не следует использовать функцию signal, ей на смену пришла функция sigaction.
У каждого процесса есть его текущая маска сигналов. Функция sigprocmask, принимая как аргумент новую маску сигналов, позволяет выполнить следующие действия: а) SIG_BLOCK - добавить к текущей маске сигналы, заданные в новой маске; б) SIG_SETMASK - установить текущую маску полностью идентичной новой маске; в) SIG_UNBLOCK - убрать из текущей маски сигналы, заданные в новой маске; К чему себя заставлять, есть же http://lurkmore.to/Windows? |
| Автор: cupper 22.3.2012, 21:58 | ||||||
А ну это то понятно было изначально. Речи об использовании этой функции не было.
Добрался до домашнего компа, и таки понял где я "промахнулся". Именно в нонимании это функции. Вернее в том что SIG_BLOCK/SIG_UNBLOCK это не указание на то что блокировать или не блокировать сигналы, как в сигнал в signal - SIG_IGN. Я не знал что у процесс изначально есть маска сигналов которая именно заблокирована. Я думал что sigprocmask как раз позволяет сделать блокировку этих сигналов. А привело меня к этому наверно неправильное толкование вот этого описания
Еще раз спасибо PS. sigaction работает на весь процесс и на каждую нить. А основное мое не поние было в том что функции sigwait и ppoll в обработке сигналов различаются как земля и небо. sigwait не позволяет выполниться обработчику (из sigaction). ppoll напротив позволяет. Гип гип ура, мать его. Наконец таки я доделаю свои долбаные пайпы. Прибольшое спасибо вам rsm, мне б к вам в ученики |
| Автор: rsm 23.3.2012, 04:53 |
Форум решает |
| Автор: cupper 25.3.2012, 19:15 | ||||||
собрав силы в кулак я вернулся к старому коду. Увидев в примере rsm использование ppoll (когда происходило блокирование при чтении в O_NONBLOCK pipe) я решил что это просто такая хорошая функция которая лучше чем pselect. Решил в этом убедится, достал свой код с select, поменял на
и тут мне стало совсем дурно, потому что операция select заблокировалась на чтении. Но это невозможно !!! Ведь просто операция чтения вставленная строчкой выше select пролетает и возвращает 0. Как ???? Как он блокируется ??? Я рву волосы на голове пытаясь вспомнить как я писал код (а я его писал, и не в одной вариации) что у мня select не блокировался, не блокировался даже на запись если я открывал pipe как O_RDWR | O_NONBLOCK. Но увы, код утерян, и я не могу получить пример в подтверждение своих слов в начале поста. Я ей богу не понимаю почему теперь select блокируется. |
| Автор: xvr 25.3.2012, 22:53 | ||
Это нормально. Ваша 'операция чтения строчкой выше' вылетела с нулем потому что был выставлен флаг O_NONBLOCK, который означает - 'операции ввода/вывода никогда не блокировать'. Но на select это не распространяется (его на то и сделали, что бы можно было ждать реального поступления данных) |
| Автор: cupper 26.3.2012, 08:48 | ||||
да ну ладно вам
|
| Автор: xvr 26.3.2012, 13:03 | ||||
Но это не значит, что он обратит внимание на флаг O_NONBLOCK. И он не обращает Так что тут надо читать так :
|
| Автор: cupper 26.3.2012, 14:58 | ||
хе |