LINUX.ORG.RU

Блокируемый сокет и несколько потоков


0

0

Интересно, как можно решить такую проблему....

Программа содержит 3 потока:

1. Поток получения данных: блокируется на сокете в ожидании данных; полученные данные помещает во входной буфер.
2. Поток отправки данных: извлекает данные из выходного буфера и отправляет их в сокет.
3. Основной поток: извлекает данные из входного буфера, обрабатывает их и помещает в выходной.

Внимание вопрос: в какой-то момент времени основной поток решает закрыть соединение (к примеру, нужно присоединиться к другому узлу). Как это сделать?

Установка флага, проверяемого из потоков не подходит, т.к. поток получения данных может быть заблокирован на read()... Есть идеи?

anonymous

> Установка флага, проверяемого из потоков не подходит, т.к. поток получения данных может быть заблокирован на read()... Есть идеи?

в таком варианте - нет, идей нет :)
но если в потоках будет блокировка на select() а не на read(), то без особых проблем можно будет добавить любой IPC по желанию и из основного потока оповещать вспомогательные о том или ином его пожелании. как пример.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

Вы имеете в виду использовать select с указанием timeout'а и по выходу из select'а по таймауту проверять значение флага? Не очень удобно, т.к. в Linux нельзя установить low-water mark и простота, характерная для блокируемого ввода-вывода теряется.

anonymous
()

>Есть идеи?

да посылай потокам сигналы. при корректной настройке сие будет жить

cvv ★★★★★
()
Ответ на: комментарий от cvv

> да посылай потокам сигналы. при корректной настройке сие будет жить
             ^^^^^^^
и иметь с этим массу развлечений? увольте :) это все-таки не процессы.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от cvv

Конечно, оригинально... Но, если мне не изменяет память, сигналы посылаются процессу а не потоку. Действительно, во времена 2.4 ядро в некоторых случаях трактовало поток как процесс (в частности, это касалось сигналов), но, к счастью, сейчас это не так.

anonymous
()

Вариант, который сейчас используется - установка таймаута на отправку и получение данных с помощью setsockopt(), но и он не лишен ряда недостатков.

anonymous
()
Ответ на: комментарий от klalafuda

>> да посылай потокам сигналы. при корректной настройке сие будет жить
>             ^^^^^^^
>и иметь с этим массу развлечений? увольте :) это все-таки не процессы.

это называется ССЗБ - кто ему доктор???
но других способов разбудить read() как сигналы и timeout я не знаю. таймаут кажись ему не подходит

cvv ★★★★★
()
Ответ на: комментарий от anonymous

> Вы имеете в виду использовать select с указанием timeout'а и по выходу из select'а по таймауту проверять значение флага? Не очень удобно, т.к. в Linux нельзя установить low-water mark и простота, характерная для блокируемого ввода-вывода теряется.

да нет, зачем опрос/таймаут. создайте между основным и рабочим потоками трубу через pipe(2) а в дочернем потоке select() на socket+pipe. после в основном потоке можно в любой момент времени что-то записать в трубу и это пробудит select() в дочернем (рано или поздно). ну а дальше уже вам виднее, что именно делать после пробуждения.

ps: понятно, что если таких потоков много (тысячи) то на всех труб не напасёшься и это кривовато. но для десятков - легко.

// wbr

klalafuda ★☆☆
()

anonymous (*) (13.09.2005 15:30:27):

Выставляешь в главной нитке из-под мутекса volatile флаг и закрываешь в ней же дескриптор, после чего (не открывая мутекса) открываешь новый дескриптор.

В читателе каждый раз после выхода из read() проверяешь из-под мутекса этот флаг и, если он выставлен, переприсасываешься (не открывая мутекса).

Кривая базовая архитектура подразумевает кривые трюки ;)

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

Вдогонку:

> Выставляешь в главной нитке из-под мутекса volatile флаг и закрываешь в ней же дескриптор, после чего (не открывая мутекса) открываешь новый дескриптор. ...после чего открываешь мутекс, ессно.

> В читателе каждый раз после выхода из read() проверяешь из-под мутекса этот флаг и, если он выставлен, переприсасываешься (не открывая мутекса).

...после чего сбрасываешь флог и открываешь мутекс, есснно.

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

2 Die-Hard: а что вы можете порекомендовать в плане изменения архитектуры?..

Постановка задачи, вкратце, такая:
Нужно написать клиент для некоторого сетевого протокола, использующего TCP в качестве транспорта. Обмен по протоколу происходит в виде сообщений - неделимой последовательности байт. Для контроля целостности соединения используется механизм "пульсации" - периодической посылки сообщений специального вида. Хочется, чтобы "внешне" все выглядило максимально просто.

Пока мысли такие:
1. При создании соединения создавать 2 потока: поток получения данных и поток отправки данных. Задачи:
* поток получения данных блокируется на read(), получает сообщение и добавляет его в конец очереди входящих сообщений.
* поток отправки данных блокируется на очереди исходящих сообщений (с указанием таймаута). Если из очереди извлечен пакет - он отправляется с помощью write(). Если истек таймаут по блокировке на очереди, то формируется пульс и опять же отправляется с помошью write().
Примечание: сейчас для сокета установлен таймаут на получение данных, поэтому в случае, если не приходят сообщения от удаленного узла соединение разрывается.

2. Для "пользователя" все просто: есть функции "извлечь сообщение из очереди входящих сообщений клиента" и "поместить сообщение в очередь исходящих"

3. Проблема возниакет, когда пользователь хочет закрыть соединение...

P.S. В рамках одного приложения может работать сразу несколько клиентов...

anonymous
()
Ответ на: комментарий от anonymous

Если бы был способ выйти из read() и write() по внешнему (по отношению к потоку), событию, то можно было бы сделать еще проще:

Сделать 3 потока: чтения, записи и сторожевой. Поток чтения делает то же самое, только таймаут для сокета не устанавливается. Поток записи блокируется на очереди до момента извлечения сообщения. Сторожевой поток формирует пульс и помещает в очередь исходящих сообщений и контролирует время, с момента прихода последнего пульса от удаленного узла. Если все плохо - разрывает соединение.

anonymous
()
Ответ на: комментарий от anonymous

>Если бы был способ выйти из read() и write() по внешнему (по отношению к потоку), событию,

для кого я писал что единственным известным мне внешним событием является сигнал????

cvv ★★★★★
()
Ответ на: комментарий от anonymous

> Постановка задачи, вкратце, такая:

[snip]

при такой постановке задачи я бы взял ACE и не особо грел себе голову ручной низкоуровневой реализацией трёхколесного друга.. ;)

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от anonymous

2anonymous (*) (13.09.2005 16:57:25):

Я бы не заморачивался с нитями, а сделал все на отдельных процессах. Читатель читает сокет и пайп от писателя select'ом. Прочитанные данные помещает в другой пайп. Писатель читает пайп от читателя (опять через select) и жужжит. Когда надо разорвать соединение, писатель кидает читателю мессагу через пайп, и читатель закрывает сокет.

Писатель -- дочка читателя, читатель -- дочка толстого сервера, слушающего порт, дескрипторы сокетов передаются через PF_UNIX сокеты.

Впрочем, возможно, я бы не стал заморачиваться с PF_UNIX сокетами, а позволил делать bind, читать и писать в сокет только "читателю"; "писатель" пусть посылает готовые данные в пайп, а "читатель" уже их ретранслирует -- такая логика проще, но содержит накладные расходы на ретрансляцию.

Это -- навскидку; разумеется, возможны варианты.

Если клиентов может быть много, я бы не стал форкаться на каждого, а с самого начала запустил бы пул с можеством пар читатель-писатель, и пусть каждая пара обрабатывает сразу несколько соединений.

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

> Я бы не заморачивался с нитями, а сделал все на отдельных процессах.

если делать в виде отдельных процессов через fork() то и пайпов не нужно, отлично работает синхронный recv() или select() с пробуждением по сигналу со стороны родительского процесса. но человеку хочется именно через потоки, от этого собственно все проблемы.. :)

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

>по сигналу со стороны родительского процесса. но человеку хочется именно через потоки, от этого собственно все проблемы.. :)

до сих пор не понял какие траблы при совместном юзании тредов и сигналов??

cvv ★★★★★
()
Ответ на: комментарий от cvv

> до сих пор не понял какие траблы при совместном юзании тредов и сигналов??

есть неплохая книжка, "A Guide to Multithreaded Programming"

http://ianzag.megasignal.com/ftp/pub/doc/books/prog/en/Prentice%20Hall%20-%20...

IMHO там описаны проблемы, возникающие при смешивании потоков и сигналов (отдельная глава).

ps: books:books

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

>есть неплохая книжка, "A Guide to Multithreaded Programming"

>http://ianzag.megasignal.com/ftp/pub/doc/books/prog/en/Prentice%20Hall%20-%20...

>IMHO там описаны проблемы, возникающие при смешивании потоков и сигналов (отдельная глава).

спасибо. но wget-om пока выкачать не смог а браузером качать неинтересно

а пока: спецификация PTHREAD всё так красиво описывает что вы меня аж заинтересовали

cvv ★★★★★
()
Ответ на: комментарий от klalafuda

2klalafuda (13.09.2005 17:46:09):

> если делать в виде отдельных процессов через fork() то и пайпов не нужно, отлично работает синхронный recv() или select()

Пайпы нужны для межпроцессорной коммуникации; они не сложнее сигналов, гораздо гибче и переносимее.

Die-Hard ★★★★★
()
Ответ на: комментарий от klalafuda

2klalafuda (3.09.2005 17:46:09):

> но человеку хочется именно через потоки, от этого собственно все проблемы.. :)

Кстати, описанная мной схема вполне работоспособна с нитями вместо процессов, даже проще -- не надо никуда дескрипторы передавать.

Другое дело, что нити по природе гемморойнее отдельных процессов...

Die-Hard ★★★★★
()
Ответ на: комментарий от anonymous

> Обмен по протоколу происходит в виде сообщений - неделимой последовательности байт.

И нафига рожать взятки? Не проще ли завести _одну_ нить, которая общается с сокетом и на чтение, и на прием, при этом сообщения для отправки она таскает из очереди, сообщения принятые складывает в очередь. Она же занимается отправкой и приемом контрольных/тестовых сообщений. Все остальные только подкладывают сообщения в очередь для отправки или выгребают их из очереди принятых. Никаких проблем, кроме синхронизации работы с очередями, но уж ЭТО не решить вообще трудно :-)

no-dashi ★★★★★
()
Ответ на: комментарий от Die-Hard

> Пайпы нужны для межпроцессорной коммуникации;

..в основном. ну а а сигналы в свою очередь - в основном для
прерывания потока исполнения процесса, последующей подчистке ресурсов
и выхода/перезапуска. хотя конечно же никто не запрещает и другие схемы.

> они не сложнее сигналов

конечно же сложнее :) если вся задача "IPC" состоит в том, чтобы
корректно и безопасно прервать блокирующую операцию в дочернем
процессе и после выйти, то IMHO проще сигнала ничего нет:

pid = fork();
if (!pid) {
    if (recv(fd, buf, len) == -1 && errno == EINTR) {
        do_some_child_cleanup();
        exit(0);
    }
} else {
    ...
    kill(pid, SIGUSR1);
    waitpid(pid, 0, 0)
}

> гораздо гибче

само собой. если требуется организовать какой-то более сложный
механизм взаимодействия между процессами, то сигналы тут слабый
помошник.

> и переносимее.

а это уже как получится (переносимее куда?) :) плюс pipe кушает дополнительные ресурсы, хотя и сравнительно мало.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

klalafuda (14.09.2005 10:02:02):

>> они не сложнее сигналов

> конечно же сложнее :) если вся задача "IPC" состоит в том, чтобы корректно и безопасно прервать блокирующую операцию в дочернем процессе и после выйти, то IMHO проще сигнала ничего нет:

Приведенный пример как раз иллюстрирует мою мысль, что сигналы -- вещь нетривиальная.

1. Попробуй сам твой код -- do_some_child_cleanup() не вызовется, а в exit статусе появится биты, соответствующие убиению процесса сигналом SIGUSR1 (SIGUSR1 -- default action is to terminate the process). То есть надо еще переопределить обработчик.

2. setsid() ты не делал, поэтому дочка вполне может словить левый сигнал, ей не предназначенный (хотя бы SIGSTOP:)).

3. Сигналы вообще вещь в себе. Например, твой коллега , разрабатывающий другой кусок той же проги, запустит таймер -- аля-улю!

Если все это корректно учесть, то код окажется далек от тривиального...

Die-Hard ★★★★★
()
Ответ на: комментарий от klalafuda

Вдогонку к предыдущему:

С "общефилософской" т.з. механизм сигналов является асинхронным. Семантика асинхронных алгоритмов по своей природе сложнее синхронных, поэтому там, где можно, лучше использовать последние. Для блокирующего чтения надежнее использовать пайп из-под селекта: как только из пайпа что-то прибежало, мы с гарантией знаем, что это именно для нас, а прочитав это "что-то" мы узнаем, почему нас разбудили.

>> и переносимее.

> а это уже как получится

Во-первых, разрые сисколов сигналами -- относительно недавняя POSIX-фича. В BSD сисколы вообще сигналами не рвуться. И в старых Линухах -- тоже.

Второе: блокирующие вызовы из stdio сигналами вообще не рвутся.

Даже чисто синтаксические проблемы имеются: sigaction() вообще относительно недавно появилась, а signal(), например, в IRIX должен возвращать int (а не void как в Линухе). А sighandler_t -- вообщеGNU extension.

Вообще, с сигналами в Линухе прямо беда! От версии к версии набор сигналов может слегка меняться; не все стандартные сигналы везде поддерживаются. А вот из man 2 signal:

The original Unix signal() would reset the handler to SIG_DFL, and System V (and the Linux kernel and libc4,5) does the same. On the other hand, BSD does not reset the handler, but blocks new instances of this signal from occurring during a call of the handler. The glibc2 library follows the BSD behaviour.

If one on a libc5 system includes <bsd/signal.h> instead of <signal.h> then signal is redefined as __bsd_signal and signal has the BSD semantics. This is not recommended.

If one on a glibc2 system defines a feature test macro such as _XOPEN_SOURCE or uses a separate sysv_signal function, one obtains classical behaviour. This is not recommended.

Trying to change the semantics of this call using defines and includes is not a good idea. It is better to avoid signal altogether, and use sigac tion(2) instead.

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

>Второе: блокирующие вызовы из stdio сигналами вообще не рвутся.

помоемому глубокое заблуждение ибо не одинь день занимался сюксем с stdio который рвали сигналы

cvv ★★★★★
()
Ответ на: комментарий от Die-Hard

> Второе: блокирующие вызовы из stdio сигналами вообще не рвутся.

ммм.. вы в этом уверены? ;)

ps: попробуйте банальный fgets() + Ctrl-C.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от Die-Hard

может. ибо glibs скорее всего местной сборки

cvv ★★★★★
()
Ответ на: комментарий от Die-Hard

> 2. setsid() ты не делал,

в дочернем чисто рабочем процессе? бог с вами, зачем тут sedsid()?

> поэтому дочка вполне может словить левый сигнал, ей не предназначенный (хотя бы SIGSTOP:)).

да. а еще можно послать SIGKILL [руками]. ну и что? как в анекдоте "а вы не чешите" :) просто так, из ниоткуда, сигналы не берутся.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от Die-Hard

граждане, это же псевдокод, зачем все понимать так буквально.. anyway.

> 1. Попробуй сам твой код -- do_some_child_cleanup() не вызовется, а в exit статусе появится биты, соответствующие убиению процесса сигналом SIGUSR1 (SIGUSR1 -- default action is to terminate the process).

обработчики сигналов наследуются -> в общем случае, это утверждение неверно i.e. зависит от обработчика в родительском процессе.

> То есть надо еще переопределить обработчик.

можно, но не обязательно, если вас вполне устраивает существующий от предка.

> 3. Сигналы вообще вещь в себе.

в отличие от pthreads - нет, в однопоточной системе поведение сигналов четко и очень хорошо определено тем же стандартом POSIX & K.

> Например, твой коллега , разрабатывающий другой кусок той же проги, запустит таймер -- аля-улю!

..и, будучи белым человеком, настроил его конечно же на SIGALRM. в дочернем процессе маскируем SIGALRM и имеем счастье.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от Die-Hard

> Во-первых, разрые сисколов сигналами -- относительно недавняя POSIX-фича.

AFAIR это было как минимум в POSIX '95.. ессно десять лем нам не срок :)

> В BSD сисколы вообще сигналами не рвуться. И в старых Линухах -- тоже.

при всем уважении к истории обоих систем, я все-таки ориентируюсь на реали сегодняшнего дня, которые гласят: всё прекрасно прерывается. хотя, при желании, может быть выставлено в SA_RESTART при вызове sigaction().

> Даже чисто синтаксические проблемы имеются: sigaction() вообще относительно недавно появилась, а signal(), например, в IRIX должен возвращать int (а не void как в Линухе). А sighandler_t -- вообщеGNU extension.

sigaction(), граждане, требуйте у вашего оператора железной поддержки sigaction() :) благо, это уже скорее must be нежели мёртва фича в стандарте.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

klalafuda (15.09.2005 10:58:35):

>> Второе: блокирующие вызовы из stdio сигналами вообще не рвутся.

> ммм.. вы в этом уверены? ;)

Да. Сам тесты гонял, посылал сигналы и следил strace'ом.

Почему-то, правда, у cvv были проблемы с SIGCHLD. Нам (с lg, если правильно помню) их тогда воспроизвести не удалось.

Die-Hard ★★★★★
()
Ответ на: комментарий от klalafuda

klalafuda (15.09.2005 11:02:12):

> в дочернем чисто рабочем процессе? бог с вами, зачем тут sedsid()?

Ну, тогда ССЗБ. Все сигналы с tty, добро пожаловать.

(кстати, setsid() "в дочернем чисто рабочем процессе" сделать весьма нетривиально!)

Die-Hard ★★★★★
()
Ответ на: комментарий от klalafuda

klalafuda (15.09.2005 11:13:55):

Ну, каждый ... как хочет (алгоритмы реализует :))

Понаступав пару лет на грабли с сигналами, я отношусь к ним с настороженностью (с пайпами проблем ни разу не возникало).

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

> Ну, тогда ССЗБ. Все сигналы с tty, добро пожаловать.

кто мешает их маскировать/обрабатывать?

> (кстати, setsid() "в дочернем чисто рабочем процессе" сделать весьма нетривиально!)

сделать то setsid() тривиально, вопрос: что будет дальше :)

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от Die-Hard

> Да. Сам тесты гонял, посылал сигналы и следил strace'ом. Почему-то, правда, у cvv были проблемы с SIGCHLD. Нам (с lg, если правильно помню) их тогда воспроизвести не удалось.

можно код примера? попробовать то не долго.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

> кто мешает их маскировать/обрабатывать?

SIGKILL?

> сделать то setsid() тривиально,

КАК сделать setsid() без двойного форка?

Die-Hard ★★★★★
()
Ответ на: комментарий от klalafuda

2klalafuda:

> можно код примера?

А что, так трудно написАть?

Впрочем, пожалуйста:

#include <stdio.h>
#include <signal.h>

void sig_handler(int sig)
{
   printf("Caught signal %d\n", sig);
   signal(SIGUSR1, sig_handler);
}

int main(void)
{
int c;
   signal(SIGUSR1, sig_handler);
   do{
      c=getchar();
      printf("c=%c ",c);
   }while(c>0);
   return 0;
}

(рекомендую гонять с-под strace)

Die-Hard ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.