LINUX.ORG.RU

мнопоточное приложение - синхронизация


0

0

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

Могу ли я открыть трубу (pipe, канал) между разными потоками, так, чтобы один писал в трубу, а другой - читал из неё. Если могу, то можно ли использовать это для синхронизации потоков? Т.е., если я скажу в одном потоке read, не зависнет ли у меня от этого вся программа?

Если нет, то я не вижу, как это решить другим способом. Блокировки, похоже, тут не помогают.

Если трубы не работают, то я не могу придумать пока что другого способа, как запись каждым потоком своих сообщений в свою очередь. Все очереди по кругу опрашивает менеджер процессов, перенаправляющий каждое сообщение в нужный поток. При этом, получается, что в каждом потоке должен быть цикл, включающий sleep, чтение своей входной очереди. И если этот sleep будет долгим, то будут тормоза, а если коротким - то тоже тормоза.

Вопрос, на самом деле, касается не программирования вообще, а конкретно SBCL и Lispworks в рамках пакета port из clocc.

★★★★★

> Могу ли я открыть трубу (pipe, канал) между разными потоками, так, чтобы один писал в трубу, а другой - читал из неё.

можете

> Если могу, то можно ли использовать это для синхронизации потоков?

можно

> Т.е., если я скажу в одном потоке read, не зависнет ли у меня от этого вся программа?

нет, не зависнет. заблокируется лишь тот поток, который вызвал read().

// wbr

klalafuda ★☆☆
()

> Могу ли я открыть трубу (pipe, канал) между разными потоками, так, чтобы один писал в трубу, а другой - читал из неё.

Да.

> Если могу, то можно ли использовать это для синхронизации потоков?

Да, но это глупо.

> Т.е., если я скажу в одном потоке read, не зависнет ли у меня от этого вся программа?

Нет.

> Блокировки, похоже, тут не помогают.

Видимо, ты неправильно их применяешь. Пример покажи.

> ...запись каждым потоком своих сообщений в свою очередь. ...

О какой очереди идет речь?

> При этом, получается, что в каждом потоке должен быть цикл, включающий sleep.

Не бывает таких циклов (в твоем случае). Подробнее расскажи.

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

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

>> Если могу, то можно ли использовать это для синхронизации потоков?
> Да, но это глупо.

нет, это [к сожалению] отнюдь не глупо. благодаря дизайну POSIX threads это фактически единственный способ реализовать универсальную многопоточную очередь ожидания событий. очень популярный дизайн.

ps: впрочем, вместо пайпов конечно лучше использовать socket pair.

// wbr

klalafuda ★☆☆
()

Во блин, тяжко тебе, товарищ den73. Я вот делаю многопоточные приложения как хочется. Ведь кто как хочет, так и дро... А при помощи труб или менеджеров - это уже не важно. Важно хорошее знание самого процесса, иначе тяжеловато поначалу будет, если вообще получится что-нибудь юзабельное кончить.

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

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

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

ps: да, тема более чем избитая и обсосанная со всех сторон, селяви.

// wbr

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

> нет, это [к сожалению] отнюдь не глупо. благодаря дизайну POSIX threads это фактически единственный способ реализовать универсальную многопоточную очередь ожидания событий. очень популярный дизайн.

Коротко: как?

Длинно: какие маны почитать, чтобы научится через пайпы организовывать эту самую.

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

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

GLib

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

> Коротко: как?

коротко - передавать события между потоками через файловый дескриптор с соотв. демультиплексором в лице select/epoll/kqueue/etc и соотв. не пользоваться условными переменными. при таком подходе есть возможность ждать события "от другого потока" и, скажем, от сокета.

другое дело, что это не всегда нужно.

> Длинно: какие маны почитать, чтобы научится через пайпы организовывать эту самую.

почитать лучше Стивенса :)

// wbr

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

> коротко - передавать события между потоками через файловый дескриптор с соотв. демультиплексором в лице select/epoll/kqueue/etc и соотв. не пользоваться условными переменными. при таком подходе есть возможность ждать события "от другого потока" и, скажем, от сокета

Жуть и боян для меня. Это уже несколько пайпов, а не один. А через сокеты: сообщение = соединение?

> почитать лучше Стивенса :)

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

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

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

ТОЛЬКО в одном случае -- когда в числе событий числятся операции с файловыми дескрипторами. Пириод.

> ps: впрочем, вместо пайпов конечно лучше использовать socket pair.

ИМХО пайпы кошернее.

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

> Подозреваю, что некрасиво, а именно, тупо проверяет в цикле доступность соответствующих ресурсов.

ну если под glib в данном случае подразумевать ф-и g_async_queue_push/pop/etc то это как раз обычная очередь на условных переменных. проблема в том, что она не работает в случае, если какой-то поток хочет ждать события на этой очереди через pop и на сокете.

// wbr

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

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

Нет такого (без неэлементарных алгоритмов). Но это единственное исключение, и проблема просто решается в два потока.

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

> ТОЛЬКО в одном случае -- когда в числе событий числятся операции с файловыми дескрипторами. Пириод.

ну естественно

> ps: впрочем, вместо пайпов конечно лучше использовать socket pair.
> ИМХО пайпы кошернее.

да в сущности пофигу. с пайпами будет создано два пайпа r+w, с сокетами - один сокет r/w.

// wbr

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

Спасибо за ответы! Я пока что ещё ничего не делаю, но действительно, есть чувство, что собираюсь сделать что-то не так, потому и вопрос :) Select в лиспе недоступен, вместо него есть listen, который мгновенно проверяет, есть ли в канале что-то, что можно прочитать. Что такое "кондишнел переменная"? Речь идёт о тредах (не о процессах).

Пример того, что ДОЛЖНО быть:

имеется три потока. первый читает данные из COM-порта. Он вызывает listen, потом sleep(1 секунда). Если данных нет 1 секунду, то это похоже на аварию, поэтому просто вызывать read и обойтись без цикла со sleep тут никак нельзя. Чтения с таймаутом в лиспе тоже нет (хотя может быть, его можно эмулировать, запустив процесс, в котором вызыван read и убить его через timeout, но я ещё это не пробовал и не уверен, что будет работать нормально).

Второй поток получает данные от первого, обсчитывает их и порождает управляющие команды для третьего потока. Также он получает ввод с клавиатуры.

Третий поток посылает данные на девайс (звуковую карту). Я пока что не уверен, что он нужен, но лучше его сразу сделать, чтобы не мучаться, если звуковая карта вдруг начнёт выполнять вызовы медленно.

Далее, касаемо мьютексов. В лиспе есть аналогичное понятие "блокировки", Так вот, для мьютекса вроде есть операция "попытка захвата", а в лиспе для блокировки такой операции не предусмотрено :(

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

Может быть, можно делать захват за два захода, используя два "мьютекса", первый из которых блокируется только на время чтения-записи из переменной, которая говорит о состоянии блокированности/не блокированности основного мьютекса? И если основной мьютекс не блокирован, то мы можем (не отпуская первого мьютекса) захватить второй мьютекс. А если он уже блокирован, то мы вернёмся без подвисания нашего потока.

Условные переменные в лиспе есть, но я не понимаю, как они работают. Там есть функции ожидания того, что "предикат станет истиной", причём, не наложено никаких ограничений на природу этого предиката. Не представляю, как это можно реализовать... Сомневаюсь, что это реализовано эффективно.

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

> ну естественно

Ну, вот, при всем к тебе уважении, виндусятник из тебя так и прет!

:-)

Зачем плодить всякий раз новые сущности, если проблемы можно решать по мере поступления комбинацией старых кубиков? Это _очень_ принципиально: перманентная смена инструментария, неизбежная при первом подходе, на уровне приложений периодически требует жуткого ничем не обоснованного рефакторинга. Последнее -- типичный "вындовсвэй". Даже слоган специальный придумали: "паровоз бежит вперед, а мы за ним не успеваем!"

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

> Ну, вот, при всем к тебе уважении, виндусятник из тебя так и прет!

стоя в одном ряду с авторами ACE и boost::asio которые на POSIX платформах используют именно этот механизм мне в принципе ничуть не зазорно :)

// wbr

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

Не, ну тут ничего сказать не могу. Может, лисперы подтянутся, просветят...

> Что такое "кондишнел переменная"? Речь идёт о тредах (не о процессах).

man 3 pthread_cond_init

Это ПОЗИКС фенька, называется "condition variable"

Лисп идет своим (могучим) путем, там все от реализации / интеллекта / расположения звезд зависит...

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

> ...стоя в одном ряду с авторами...

(немного в сторону) А вот это тоже принципиально: порочная идеология от M$ глубоко проникла в массу ГНУ сообщества. Достаточно вспомнить деИказу. Достаточно посмотреть на GNU libc 6: это (ИМХО) совершенно пионЭрская поделка (по сравнению с GNU libc 5).

(предупреждаю -- троллей кормить не буду)

> ...ACE и boost::asio которые на POSIX платформах используют именно этот механизм...

Это ИМХО немного другое, кроссплатформенность. Qt, например, тоже далеко ушла от ЦеППшного стандарта. Но это лишь доказывает живучесть стандарта.

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

> Это ИМХО немного другое, кроссплатформенность. Qt, например, тоже далеко ушла от ЦеППшного стандарта. Но это лишь доказывает живучесть стандарта.

ну это таки не столько кросплатформенность сколько в первую очередь заданный pattern. есть reactor есть proactor, от них и пляшут. то же, что конкретно на POSIXе для их реализации требуются одни приседания [а на какой-то другой платформе вполне возможно другие] - это уже дело техники.

// wbr

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

> ...Он вызывает listen, потом sleep(1 секунда).

Все, тут ИМХО явная логическая ошибка. При любом раскладе потоку нет ни малейшего смысла вызывать sleep после listen.

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

> сколько в первую очередь заданный pattern.

А паттерны всегда диктуются реализацией.

Я про это и имею в виду, "обвиняя" тебя в "виндусизме". Ты (ИМХО) привык к иным паттернам, чем те, что приняты в "нормальном" ПОЗИКСе. Например, паттерн с мультиплексором событий на пайпы с последующим селектом ты явно считаешь "костылем" -- а почему? Это нормальный дизайн паттерн!

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

> Я про это и имею в виду, "обвиняя" тебя в "виндусизме". Ты (ИМХО) привык к иным паттернам, чем те, что приняты в "нормальном" ПОЗИКСе.

с учётом того, что я пользовался ими в лице Win32 последний раз порядка десяти лет назад то да, привычка видимо очень сильно запала в душу :)

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

> Например, паттерн с мультиплексором событий на пайпы с последующим селектом ты явно считаешь "костылем" -- а почему? Это нормальный дизайн паттерн!

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

в POSIX что касается потоков я наблюдаю лишь одну проблему - это некоторая неоднородность API потоков и API доступа к файлам. в результате этой неоднородности для того, чтобы реализовать ту или иную схему взаимодействия потоков друг с другом иногда приходится прилагать определённые усилия. само по себе как абсолютная сущность эти усилия не костыль. ну приложил, ну сделал. если же сравнивать с другими решениями в том числе с пресловутым Win32 в котором API в этой части существенно однороднее by design, то может быть и смотрится как костыль.

не знаю, почему именно POSIX пошёл этим путём. вполне возможно что в угоду производительности. с файловым дескриптором в user space делать в общем то совершенно нечего. его можно лишь передать в системный вызов, который уже знает что это за объект и как с ним работать в том числе ожидать событие. это лишний вызов в ядро. в свою очередь, если посмотреть на реализацию pthread_mutex_lock(), то там многое делается в user land, к примеру, взятие не заблокированного мьютекса, за счёт чего по всей видимости достигается существенное удешевление этой операции. с файловым дескриптором такой финт ушами уже не пройдёт бо весь объект включая его внутренние структуры и сам лок находится в ядре и недоступен из libc. не удивлюсь, если окажется, что аналогичный подход к реализации принят не только в glibc но и в других UNIX like системах -> в конечном итоге он вылился в обобщённое API включённое в POSIX.

цена отхода от файловых дескирпторов к внутренним структурам в user land в угоду оптимизации - это полученная неоднородность API, которая в результате не позволяет напрямую оперировать в штатных демультиплексорах событий типа select объектами обоих типов [файловые дескрипторы + условные переменные].

само по себе это не так страшно и если такой функциональности не требуется, то и вовсе не страшно. но когда она нужна, начинается некоторый геморой, который с точки зрения конечного пользователя API выглядит прямо скажем несколько искусственным и надуманным. хотя если приложить определённые усилия, то и он обходится/скрывается. но это уже относится к уровню тулкита а они по понятным причинам далеки от какого то стандарта или совместимости друг с другом.

// wbr

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

> > Т.е., если я скажу в одном потоке read, не зависнет ли у меня от этого вся программа?

> нет, не зависнет. заблокируется лишь тот поток, который вызвал read().

Может еще зависнуть та нить, которая сделал write в канал, из которого никто не читает.

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

> Может еще зависнуть та нить, которая сделал write в канал, из которого никто не читает.

ну это само собой :)

// wbr

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

> Все, тут ИМХО явная логическая ошибка. При любом раскладе потоку нет ни малейшего смысла вызывать sleep после listen.

Так listen не имеет таймаута. Это не тот listen, который "слушать соединения на сокете", это "проверить, что в канале ввода что-то есть". И у него нет таймаута. Если я запущу его в цикле, то это будет "почти пустой" цикл. А таймаут мне нужен - иначе я не выявлю аварию. Ты хочешь сказать, что таймер должен тогда быть в отдельном потоке, а вместо listen я должен использовать просто read? В моём случае это, конечно, прокатит, т.к. при аварии все потоки всё равно будут убиты и этот read умрёт при смерти потока. А так, вообще говоря, не годится.

И вообще, всё достаточно плохо. "Функции ожидания" в лиспворксе работают тупо - они периодически вызываются. Даже если это происходит не слишком часто, всё равно получается не идеально. Есть более тонкие средства (очереди сообщений) и я надеюсь, что они работают правильно, но они отсутствуют в port.

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

В общем, короче говоря, я более-менее придумал, что мне делать. Буду использовать "функции ожидания". Они есть в port. Пусть они работают не оптимально, зато они переносимы. С их помощью я сделаю "переменные ожидания".

Правда, заподло состоит в том, что port не работает с sbcl. Так что переносимость у меня получается пока что воображаемая. Придется, видимо, стать контрибьютором open-source и сделать порт для sbcl.

А чтение, наверное, передалаю на read-char и плюс к тому будет отдельный поток со sleep, который будет следить за таймаутом. Хотя это потом.

Всем спасибо! Я узнал довольно много нового!

den73 ★★★★★
() автор топика

> Select в лиспе недоступен

> Чтения с таймаутом в лиспе тоже нет

> "Функции ожидания" в лиспворксе работают тупо

> port не работает с sbcl

При всём уважении к лиспу, Вам не кажется, что язык не подходит под задачу?

Впрочем, даже если без лиспа никуда, может быть стоит скомбинировать лисп с C? Написать низкоуровненые функции на C с использованием glibc или чего ещё и вывести интерфейс к ним в лисп с помощью swig и/или ffi?

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

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

http://clocc.sourceforge.net/dist/port.html

Многие вещи (в т.ч., треды) не стандартизованы. port пытается привести это к обещму знаменателю. У него это во многом получается, но, похоже, он перестал поддерживаться ещё до того, как sbcl вошёл в моду.

В sbcl есть довольно хорошая (на беглый взгляд) поддержка тредов, http://www.sbcl.org/manual/Threading.html#Threading

Открою страшную тайну - я сейчас вообще работаю в оффтопике. Так получилось, простите меня, люди... Я опасаюсь, что ставить современный линукс на pentium-I с 48 метрами памяти и небольшим HDD - это целая задача на полдня или больше, а Win98 там уже стоял.

Кроме того, да, работа с COM-портом и аудиокартой будет написана в виде отдельных программок на С (абсолютно виндо-зависимых). Лисп общается с ними через трубы. Можно было сделать в виде библиотек и прикомпоновать, или сделать через SWIG, но через трубы как-то надёжнее (заведомо лисп не рухнет, а когда он падает - это всегда больно). Для Com-порта я прогу уже написал, для аудио - ещё нет.

Но, тем не менее, я стараюсь поддерживать лисповую часть переносимой, т.к. планирую глобально сваливать на SBCL. Увы, и это пока что не получается. Я уже вовсю использую очереди сообщений (думается, это самое правильное). Утешает только то, что в SBCL тоже всего навалом (в том числе, и очереди сообщений есть).

В общем-то, я убеждён, что под эту задачу язык как раз подходит (как и под вообще большинство задач). Если бы нужно было что-то более реалтаймовое, когда нельзя было допустить потерю времени на сборку мусора - вопрос был бы спорным. А так - всё ОК

den73 ★★★★★
() автор топика

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

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