LINUX.ORG.RU

Отношения потоков


0

1

Всем привет, вот такой у меня вопрос: есть два потока, главный и подчинённый, главный назначает задачу подчинённому таким образом: в очередь задач (о подробностях её реализации говорить не буду) записывает действие, потом разблокирует мьютекс и подчинённый поток прочитает задачу в очереди и начнёт её выполнение. Существует 3 варианта событий: 1) Случится событие из за которого главный должен будет немедленно пришлёпнуть подчинённого например отмена операции). Здесь соответствующая команда посылается (через очередь) подчинённому потоку и он сам принимает решение о завершении, в случае если он при этом зависает или слишком долго завершает себя главный поток просто его убивает. 2) Задача выполняется до конца, после чего подчинённый поток информирует (через очередь) о завершении задачи и главный поток блокирует через мьютекс подчинённый до появления новых задач. 3) в потоке возникла ошибка (исключение) о чём посылается запрос ( через очередь) и главный процесс принимает решение ( выполнить задачу заново, перезапустить поток, и тд.). Во первых прошу оценить такое общение между потоками через некую защищённую область обладающую допустим методами addMessage, getmessage. Во вторых во время выполнения задачи я не могу с помощью мьютекса блочить главный процесс так как в случае ошибки в подчинённом он зависнет навсегда и к тому же он должен прослушать сообщения из других каналов, ну так вот, чтобы он не жрал слишком много ресурсов и в тоже время выполнял свою функцию я использую usleep(10*1000) или sleep(10) так как приложение взаимодействует с пользователем то это нормальный «пинг». Так вот, мне тут пришла в голову мысль что это не самый нормальный способ усмирять аппетит главного потока скажем так, Что вы думаете о такой схеме взаимодействия?

P.S. Извиняюсь за много букв :)


Во первых прошу оценить такое общение между потоками через некую защищённую область обладающую допустим методами addMessage, getmessage.

Всё уже придумано до нас.

korvin_ ★★★★★
()

Во-первых, все ожидания событий делаются на условных переменных, а не на мьютексах. Во-вторых, ожидание главным потоком результата или исключения делается на одной условной переменной. В-третьих, поток обработчик должен ловить все исключения и корректно их передавать основному потоку. Дальше начинается специфика ЯП. Т.к. ты язык не указал, то отсылаю к манам по std::condition_variable, std::mutex, std::future, std::exception_ptr и std::packaged_task.

Begemoth ★★★★★
()

Зачем тебе потоки? Ты там указателями на гигабайты данных обмениваешься или что? Если твоя „задача“ - это четырёхбайтный код комадны и восемь байт параметров, то лучше старый добрый fork, dup2 и poll.

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

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

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

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

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

Тут возникает вопрос как поток обработчик выполняет задания: последовательно (считал из очереди -> выполнил -> записал ответ -> считал из очереди -> ...) или как-либо образом пытается выполнять несколько заданий одновременно?

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

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

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

очередь запросов представляет собой одностороннюю очередь с защитой от переполнения

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

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

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

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

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

канал сообщений который поток исполнитель регулярно чекает

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

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

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

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

Ты не понял, о чём я? Что мешает форкнуть кучу „модулей“, засунуть их пайпы и poll и ждать ивентов? Какая вообще разница, сколько они выполняют свои задачи?

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

Я тебе серьёзно говорю. Если у тебя „задача“ - двадцать байт, запусти кучу модулей, которые процессы, и общайся с ними через stdin/stdout. В модуле читай из stdin, пока не ноль,выполняй и шли ответ. А в мастере сделай poll для пайпов всех модулей (плюс один контрольный например) и обрабатывай чего там тебе из них приходит. С процессами намного удобнее работать потому-что:

  • Процесс в себе содержит все ресурсы, которые ему необходимы. Если убить процесс, ресурсы автоматом освобождаются.
  • В случае креша одного модуля (только царь может так писать, что-бы впервые откоНпилированый код на С не падал), всем остальным пофиг. Мастер просто перезапускает упавший и шлёт ему задание ещё раз например (зависит от стратегии).
  • Всё модульно и при желании можно запускать/обновлять модули без остановки всей системы.

Потоки реально нужны только тогда, когда у тебя шаред мемори с уродскими глобальными переменными например или „задачи“ размером > 1 МБ.

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

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

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

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

Пока ты не прочитаешь man epoll, ты нихрена не поймёшь о чём я тебе пытаюсь сказать.

я не могу делить адресное пространство между процессами

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

тоже и с потоками будет

Перезапуск потока намного сложнее. Есть немалое количество нюансов.

а твоя идея во время выполнения постоянно отсылать сообщения главному потоку

Чё? Что значит постоянно? Что ты имеешь ввиду?

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

ну в общем механика как poll, смотри, при общении между файлами через дескриптор при записи чтении проверяется состояние дескриптора, при ошибке в дескрипторе write/read ловит ошибку, а poll её обрабатывает. Грубо говоря вот такая ситуация

if(read(responce_data))
{
   while(responce_data) // == 1
   {
        // некие операторы
   }
}

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

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

грубо говоря мы получили данные и это привело к бесконечному циклу

А зачем ты в цикле дальше читаешь? Читай сколько пришло и запоминай. А потом опять poll. Когда назапоминал на корректное запрос/ответ от конкретного дескриптора - обрабатывай.

И что хуже всего мы не можем прочитать внутреннее состояние модуля из за того что он в другом процессе

Без обид, но у тебя каша в голове. Ты поток фиг корректно остановишь если он не в cancellation point. А процессу достаточно послать сигкил и словить 0 из дескриптора стдаута. Потом перезапустил и вуаля. Кроме того, как ты состояние потока будешь анализировать? В стеке у него ковыряться? А, точно! Глобальные переменные! Эпический говнокод у тебя будет, по которому ты утомишся с дебагером бегать.

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

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

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

В случае неисправимой ошибки поток убивается а все данные затираются без проблем так как все потоки созданы методом Init и у него есть все деструкторы

Ладно, убедил)

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

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

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