Pull to refresh

Comments 41

wait(); // неограниченное ожидание завершения потока

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

void waitForEndThread(QThread *obj, unsigned long time)
{
    while (!obj->wait(time)) {
        QCoreApplication::processEvents();
    }
}

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

ThCounter::~ThCounter(void) {
    bIfRun = false;
    QThread *obj = QThread::currentThread();
    obj->quit();
    bool w = obj->wait(10000);
    if (nIdTimer) killTimer(nIdTimer);
    nIdTimer = 0;
}

От следующего

ThCounter::~ThCounter(void) {
    bIfRun = false;
    quit();         // корректный способ завершения потока
    wait(10000);         // ожидание завершени потока (неограниченное ожидание wait())
                    // подробнее см. Боровский А. Qt4.7... стр.170
    if (nIdTimer) killTimer(nIdTimer);
    nIdTimer = 0;
}

А они отличаются. Функциональностью. ОБъясню в чем проблема. Нижний "зависает". на wait(n). Верхний - нет. Для меня - загадка. Казалось бы, если я задал параметр методу wait, то на нем и "вис" будет это же время. Так работает нижний код. ВЕрхний почему-то работает иначе, т.е., несмотря на указанный параметр, выход из wait происходит тут же. Это и позволяет не блокировать интерфейс. Можно грешить на то, что возвращается указатель не на текущий поток. Пусть так. Но все равно - почему фукции wait отрабатывают по-разному?

Повторюсь. Мне хотелось бы знать, как быстро "убить" поток. Но как?

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

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

ThreadWorker worker; // либо через оператор new
worket.init(setup_params); //необязательное. setup_params набор параметров, которые надо отдать в поток
worket.start(); //начинается выполнение содердимого переопределенного метода run()
worker.waitForEndThread(100); //мой метод, описанный выше
//... other code

В строке 4 внутри метода каждые 100 мс wait() будет сбрасываться в true и будет обрабатываться очередь сообщений (сигналы слотам). Потом в цикле все будет повторяться, пока не будет выполнен выход из метода run().

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

Вообще, потоки - специфическая вещь и для каждой конкретной задачи нужна своя реализация.

Пока я писал ответ, просмотрел весь ваш код - в вашем случае, возможно, стоило создавать потоки через moveToThread(). Остальное советую глянуть в справке по Qt - там есть примеры.

В деструкторе ThreadWorker::~ThreadWorker() удалять динамически созданные члены класса, закрыть используемые ресурсы и т.д.

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

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

Автомат - это независимая задача, которая выполняется внутри процесса и ... далее по тексту.

Вся разница в том, что поток - это поток, а автомат - это внутри одного потока. Именно так это сейчас, если говорить об автоматах. Другая разниза - .в модели управления. У потока - это блок-схемная модель, у автомата - это соотвественно автомат.

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

Предположим, я решусь на подобное. Сейчас автомат - это потомок моего автоматного класса LFsaAppl. Сделать его еще и потомком класса QThread - пару секунд. Т.е. уже это сделано (см. мою статью). Но замена "двигателя" сразу породила проблемы: что-то перестало работать. Я уж молчу про саму отладку, с которой у меня с автоматами совсем нет проблем.

И вот я возился с QThread больше месяца, постигая все чудачества потоков. И тут появляется, как я понял, его аналог - ThreadWorker. И где гарантии, что я не приду к такому же итогу?...

А потому. за информацию огромнейшее спасибо. Отойду от QThread и возьмусь, может быть, за ThreadWorker. Но подозреваю, что поменяв "двигатель" я буду сильно жалеть о старом ;) Ведь, на нем, кроме скорости, меня все устраивало. А тут со скоросьтю будет моментами все просто прекрасно, но без каких либо гарантий доехать до места.

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

Пока же, чтобы разгрузить автоматное ядро, я проверенные автоматы перевел на таймеры (а планы были на потоки). Так у меня теперь работает весь севис среды ВКПа. Это проверенный и всегда (пока проблем не было) работающий примем. Но стоит только перейти какому-то процессу на поток - бац! - проблемы. Конкретный пример. В КПа есть процесс, который я называю осциллограф (все графики на нем) . Был автоматным - работал. Стал таймерным - работает. Переношу на поток - не работает от слова совсем. И попробй докопаться. Хотя именно только включение одного осциллографа тормозит ядро больше чем на порядок. На том же тесте счетчиков имеем: дискретный такт без осциллографа - 0.28 мсек, а с ним - 2.8 мсек. Но все мои заходы на "поточную графику" пока ни к чему не приводят. Хотя и совсем бесполезными их не считаю :)

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

  • создание локальной переменной, ее инициализация (параметры, связывание через connect())

  • запуск

  • вызов exec()

In the Qt Documentation:
int QThread::exec()
Enters the event loop and waits until exit() is called, returning the value that was passed to exit(). The value returned is 0 if exit() is called via quit().
This function is meant to be called from within run(). It is necessary to call this function to start event handling.
Note: This can only be called within the thread itself, i.e. when it is the current thread.

О, есть цикл обработки событий внутри дочернего потока.
Идем далее.
У меня нет полных исходников примера из Боровский, но переменная thread объявлена в классе главного окна. Ее срок жизни - срок жизни приложения, т.е. главного потока.
Смотрим, что будет при завершении приложения.
Смотрим деструктор:

  • проверка того, что thread != nullptr

  • вызов quit()

  • вызов wait() //с дефолтными параметрами

In the Qt Documentation:
void QThread::quit()
Tells the thread's event loop to exit with return code 0 (success). Equivalent to calling QThread::exit(0).
This function does nothing if the thread does not have an event loop.
Note: This function is thread-safe.

bool QThread::wait(QDeadlineTimer deadline = QDeadlineTimer(QDeadlineTimer::Forever))
Blocks the thread until either of these conditions is met:
The thread associated with this QThread object has finished execution (i.e. when it returns from run()). This function will return true if the thread has finished. It also returns true if the thread has not been started yet.
The deadline is reached. This function will return false if the deadline is reached.
A deadline timer set to QDeadlineTimer::Forever (the default) will never time out: in this case, the function only returns when the thread returns from run() or if the thread has not yet started.
This provides similar functionality to the POSIX pthread_join() function.

Ага, есть метод отправки сигнала (QThread::quit()) на завершение выполнения дочернего потока, которую примет цикл обработки событий (QThread::exec()).
После получения сигнала на завершение, будет выход из метода run(), который по сути означает завершение выполнения потока и освобождение ресурсов.

Мы ждем корректного завершения потока (QThread::wait()) и освобождения связанных с ним ресурсов во избежание их утечки. Так как в примере в потоке ничего особенного не выполняется, то задержка на wait() будет мизерной.

Код примера из Боровский полностью корректный для использования в той ситуации.

Теперь смотрим на ваш код (Листинг 1).
Внутри метода run() есть цикл обработки данных и нет цикла обработки событий.
Внутри FThCounter::~FThCounter() и FThCounter::WaitForThreadToFinish():

  • вызов quit()

  • вызов wait() //с дефолтными параметрами

Касательно разницы в работе. Надо смотреть под отладчиком, что именно возвращает QThread::currentThread() и есть ли потоки с таким ID (см справку).

Метод quit() отправляет команду на завершение выполнения дочернего потока. Вот только чем и где он будет принят - я сказать не могу. У меня только одна ассоциация: "Uciekł gdzie pieprz rośnie" (в неизвестном направлении). Нужно смотреть исходники QThread, но ваш код - потенциальный UB.
Метод wait() - может ждать бесконечно долго или сразу возвратить true, в зависимости от состояния экземпляра класса FThCounter.

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

Мне хотелось бы знать, как быстро "убить" поток. Но как?

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

Как бы то, что есть, меня фактически устраивает, но... есть определенные сомнения. Особенно по поводу сообщений. Они возникли пости сразу, т.к. когда, я во внутрь run() вставляю exec(), то на нем все и виснет. Это сразу видно в отладчике. При удалении объекта-потока процесс идет дальше. Использую такую хитрость можно, конечно, все же запустить. Но поток-то запустится, но тогда прекратится обработка сообщений. Или нет? Что здесь не так?

Вы хоть читали документацию?

Enters the event loop and waits until exit() is called, returning the value that was passed to exit().

exec() это цикл обработки событий! На нем все будет виснуть, т.к. он обрабатывать только очередь событий. Ваш случай - мой вариант waitForEndThread() или изменение архитектуры приложения так, чтобы в quit() или wait() не было необходимости.

PS. Если это какой-то коммерческий/полукоммерческий проект - наймите фрилансера с необходимыми скиллами.

Что-то не нашел класса ThreadWorker? Где он находится или из какой библиотки взят?

Это просто абстрактный пример класса, который реализует поток. Хотя, примеры взяты из кода моих проектов.

"Для порождения потока в Qt нужно создать потомок класса QThread и перегрузить его метод run(). "
Разве от этого многообразие средств Qt будет утеряно. Вот нужен мне в этом отдельном потоке QTimer... И как его использовать? А механизм signal-slot будет работать в экземпляре класса с переопределенным run? Может быть лучше использовать thread and worker подход? Я написал такой пример. Он использует thread and worker подход, но в отличие от всего того, что встречал в интернете, у меня смарт points. Хотел бы получить конструктивную критику по своему примеру...

...Разве от этого многообразие средств Qt будет утеряно.

С Qt все останется так, как и было. Станет только лучше - появятся автоматы в дополнение к потокам и всему тому, что есть в Qt.

Вот нужен мне в этом отдельном потоке QTimer... И как его использовать?

С этим похоже, действительно, проблемы. А вот один таймер в поток устроен по умолчанию. С ним и его использованием проблем нет. Так что не все так уж плохо.

Хотел бы получить конструктивную критику по своему примеру...

Если вопрос ко мне, то пока я не дорос до такого. В потоках я еще только начинающий ;)

Словил очередной диссонанс от вашей статьи. А в это раз ещё и от коментов к ней.
Ощущение что и статью и коменты писал ChatGPT.
Или может вы просто издеваетесь/прикалываетесь над читателями? Как можно заявлять о нескольких десятках лет опыта разработки программ и при этом выдавать такие наивные куски кода?

Листинг 3
Ваши потоки будет молотить воду в ступе нагружая ядро процессора пустым циклом, пока какой-то внешний "контроллер" не выставит bIfExecuteStep в true. Но это только на один прогон тела цикла, дальше опять будет бессмысленая пустая работа. Разве что вы укажете подходящую задержку для usleep, чтобы она примерно соответствовала частоте работы "контроллера", который разрешает потоку делать полезную работу.

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

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

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

Спасибо за совет. А "воевал" я и создавал код, чтобы показать работу потоков. И лучше, и даже нужно, это делать на простом примере.

Но в целом Вы разобрались в коде. Поздравляю ;) Все верно. Код на листинге 3 работает именно так и именно так задумано. А задуманок, чтобы потоки работали полностью синхронно, и чтобы тактировать это со стороны (средой ВКПа). Цель - смоделировать работу синхронной сети параллельных процессов. Без этого сеть асинхронна и это уже другая модель вычислений.

Листинг 4 тоже Вами понят правильно. Только не понята цель - работа потоков с общим ресурсом (его моделирует счетчик), к которому нужно ограничить доступ только одним потоком. Были по хоту рассмотрены разные варианты такого доступа и то, сколько они "стоят". Мютексы, действительно, "жрут" прилично. Но это только одна сторона вопроса. Другая - сравнить с эффективности такой же параллельной работы, но организованной на одном потоке. А если конкретно - сравнить с ВКПа. Т.е. оценить эффективность последней в сравнении с потоками. Что и было сделано.

В конце концов, работа со счетчиком - это лишь иммитация работы в потоке. Да, она элементарна. И что с того. Вместо этого можно придумать что-то посложнее, но тогда и Вам быо бы сложнее разобраться с кодом. Так в этом смысле счетчик - самое то ;) А надо - не надо это вопрос риторический. А что делать, когда надо?. Под каждое "надо" придумывать что-то свое? Но кроме сетей автоматов (ВКПа) и просто потоков в такой ситуации сложно мне что-то представить. Предлагайте - обсудим. Было бы интересно. Кроме "беременных женщин", конечно. ;)

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

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

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

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

"Параллельная" работа в одном потоке - это, как я понял, сарказм? :-)

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

Из параллельных моделей могу привести, например, из известных - ярусно-параллельную модель, сети Петри. Больше примомнить сложно... Но, заметьте, многопоточности среди них нет. Многопоточность - структурная модель. Она не вычислительная модель. Не алгоритмическая, если хотите. Так что потоки - это структурный механизм для реализации вычислительной модели. Программист "живет" в рамках алгоритмической/вычислительной модели. Но это больше справедливо по отношению к последовательным програмистам. Просто потому, что они используют соотвествующие последовательные языки.

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

Это я к чему так долго... В рамках ВКПа реализована параллельная вычислительная модель. Текущий ее вариант реализации - однопоточный. Но - тпру! - это пресекаю возражения! Хотя и однопоточный, но параллельный по сути. Я протестировал потоки на предмет реализации на них параллельной модели вычислений. К сожалению, с наскока не получилось: реализация получается сложнее, а эффективность, если не хуже. Пока получается, что большого смысла в этом нет. Но по ходу появилиь варианты увеличения эффективности существующей реализации. Так что время было потрачено не зря. Так что "психанул" не без пользы :)

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

И это правильно ;).

Вроде вы назвали две параллельные модели, но тут же говорите что их нет, но при этом у вас в ВПКа оно как-то реализовано. :-)

Кстати, что такое ВПКа?

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

Но если модели якобы нет, то в чём смысл всего этого? Вот есть у нас в наличии структурные механизмы и вполне себе работающая "модель" - разделяй и властвуй. Бери большую задачу и разбивай её на маленькие, не пересекающиеся, и запускай их в разных потоках/процессах.

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

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

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

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

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

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

Например проблема со счётчиком, с которым работают несколько потоков. В однопотоке надо специально сделать всё так, что бы разбить процесс инкремента на 3 части:

  • читаем значение счётчика из общей памяти куда-то себе во внутреннее состояние процесса;

  • прибавляем единицу к значению, которое хранится внутри процесса;

  • записываем значение из процесса обратно в общую память.

Только в таком варианте вы сможете в однопотоке модельно воспроизвести то, что может случится в мультипотоке. А если вы реализуете инкремент простым
x += 1
то не заметите никаких проблем.

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

Ну и как итог, ...

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

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

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

Конечно, определенные проблемы параллельных вычислений остаются и на уровне модели вычислений ВКПа, но это, скажем так, проблемы более высокого уровня, чем проблемы потоков. Это отличие сравнимо с отличием программирования на ассемблере с програмированием на языке высокого урровня. Понятно, что здесь уровень ЯВУ представляет модель вычислений ВКПа ;)

Да, и, пожалуй, самое главное. Почему все же автоматы (точнее, сети автоматов), а не потоки?

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

Какие еще доказательства нужны, что первое, т.е. автоматы, это правильная модель параллельных вычислений, а второе, т.е. потоки, как минимум, ошибочное представление о параллелизме ? ;)

Вот вы опять пишете про какую-то модель параллельных вычислений которая, как я понял, скрывает от разработчика наличие реальных параллельных процессов на физическом уровне. Но что это такое? Мой Гугл про это ничего не знает.
Как я понял, в небольших кусках кода, можно применять что-то вроде ярусно-параллельной формы записи алгоритма для автоматизированного поиска в коде тех мест, которые можно распараллелить. Такое можно в теории использовать в компиляторах и процессорах.

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

Давайте на реальном примере. Как ваше решение будет "скрывать" наличие где-то в ядре потоков при инкременте общего счётчика? Разработчик, использующий ваше решение, при этом не "профессор". Он в общем случае просто напишет в своём коде на C/C++
*x += 1;
Что помешает ему это сделать?

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

Мой Гугл про это ничего не знает.

Нужно читать правильные книжки. Какие? В моих статьях на них есть ссылки.

Что помешает ему это сделать?

Ни что ;) Только в потоке это будет строка кода, которая может быть прервана в любом месте. А в ВКПа эта строка будет принадлежать действию, которое ни кто не может разорвать на части (как у потоков). Формально любое действие неделимо. Это свойство .модели.

И про дискретность возникают вопросики. 

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

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

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

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

Разбивать решение на более мелкие действия, которые бы позволили уменьшить длительность дискретного такта. Но, если честно, на "высоком уровне" не надо беспокоиться об эффективности использования процессорного времени. Если Вас устраивает скорость дискретных процессов, то о каком счастье еще можно мечтать? Не царское это дело - опускаться программисту до отдельных инструкций. Пусть это беспокоит тех, кто создает ядро, реализующее модель параллельных вычислений. Каждому свое. Программисту - вычислительная модель и ЯВУ, архитектору - ассемблер и что там еще ниже - набор команд, шины, память и т.п. ;)

Еще раз. Вы думаете, что у меня ядро на одном потоке? Враки - я пошутил ;). Я сам не знаю, как реализовано ядро ВКПа. Но зато я знаю, как описать и создать автомат в рамках ВКПа, как визуально, так и на ЯВУ. Зачем мне думать еще о чем-то? Тем более об эффективности "использования процессорного вемени". Я об этом начинаю думать, когда решение начинает тормозить. И то только так - как поменяь комп на более шустрый, увеличить объем оперативной памяти и что-то в этом духе. Хотя, конечно, постараюсь, чтобы до этого дело не дошло. И тут тоже есть над чем подумать и есть варианты более эффективность реализации. Но это уже область искусства программирования на платформе ВКПа, владения ее моделью вычислений :)

Только в потоке это будет строка кода, которая может быть прервана в любом месте.

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

Получается в вашем случае действия являются атомарными? Наверное это обеспечивается использованием мьютексов. Только вот как понять какой именно мьютекс надо использовать? Одно дело когда мы пытаемся одно и то же действие выполнить параллельно в два потока - можно просто запретить это мьютексами для этого конкретного действия.
Но если два разных действия используют общую память? ВКПа как-то отслеживает наличие обращений к общей памяти из разных "действий" и втыкает куда надо мьютексы? Или может в ВКПа вообще нет понятия "общая память"? Каждое действие работает со своей локальной памятью, а "общаются" между собой они через "обмен сообщениями"?

потоки ничто не прерывает

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

Получается в вашем случае действия являются атомарными?

Именно так. А что обеспечивает - дело техники. Но мютексов точно нет . Зуб даю! :)

...  Или может в ВКПа вообще нет понятия "общая память"?

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

Прерывание выполнение потока не важно в этом контексте. Результат тот же самый, если бы его никто не прерывал, а просто в соседнем ядре процессора работал второй поток.
Прервётся текущий поток в ядре ЦПУ для выполнения другого потока, или оба потока будут выполняться одновременно - это не известно, и принимать на основе этого решения можно только в случае эксклюзивного доступа к железу и полному контролю надо процессором.

Основная проблема в том, как работают процессор и память. А работают они в 3 этапа: прочитать, вычислить, записать. Именно из-за этого и не получается без бубна/мьютексов/хитрых-алгоритмов атомарно выполнить инкремент ячейки памяти на многоядерных процессорах.

Как это "я не знаю как реализовано ядро ВКПа"? Это разве не ваше детище?

Глянул статью с описанием того как работать в ВКПа. Не нашёл ссылку на исходники.

Конечно, я шучу. Я знаю все. Но мог бы и не знать. Я об этом. Могла бы быть и такая версия, правда? А поэтому, когда Вы выдвигаете версию об однопоточной реализации ВКПа, то, по большому счету, это всего лишь Ваше предположение. На уровне ВКПа вообще нет понятия потока. Кстати, когда-то ядро было реализовано на ассемблере и единтвенно, что было нужно, - система прерываний. Исходный код, действительно, пока недоступен, а потому я могу говорить, что угодно ;) Проверить-то это невозможно? Пока. Пока расклад такой, а как будет дальше зависит не только от меня или моих желаний.

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

Ну, что ты сделаешь, блин, соврал! :( Но в этот раз не специально и не в шутку, а просто подзабыл, что уже когда-то было сделано. Одна из первых моих статей называлась "Мультипрограммная система без прерываний на базе микроЭВМ "Электроника-60". И, что интересно, она работала! Так что не нужны даже прерывания. Т.е. то, на чем многопоточность базируется. Я что-то не слышал о потоках, которые не использовали бы аппаратные прерывания для рализации своей функциональности.

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

Поводу того, что бы объединить "автоматы" и "потоки" - это вам прямой наводкой в современные асинхронные фреймворки. Они позволяют запускать "корутины" как в одном потоке, так и распределять их выполнение по разным потокам.
Не знаю на счёт таких фреймворков в C++ и как они дают гарантии, что не будет проблем с выполнением корутин в разных потоках (наверное ни как - программист должен сам за этим следить). Но как минимум в Rust есть фреймворк tokio и он за счёт особенностей языка позволяет на этапе компиляции предотвратить попытку запуска в другом потоке кода, с которым нельзя так делать (который не обеспечивает мультипоточной безопасности).

Поводу того, что бы объединить "автоматы" и "потоки" - это вам прямой наводкой в современные асинхронные фреймворки. Они позволяют запускать "корутины" как в одном потоке, так и распределять их выполнение по разным потокам.

Ни какие подобные фреймворки ни какие "современные параллельные языки", даже типа Rust, не реализуют параллельную модель. По крайней мере я таких не знаю. А там, где есть слово "потоки" сразу говорю - нет параллелизма. И я уже объяснил почему.. А в очередных вариантах "просто автоматов" или "просто потоков" даже обеспечивающих "мультипоточную безопасность" просто мало смысла. Это вполне понятный и давно уже пройденный этап. Кому-то это очень интересно они видят в этом какой-то смысл. Мне - нет. Ни с точки зрения интереса, ни с точки зрения смысла. Попытаться использовать, как те же потоки, - еще можно попробовать. Пока эксперименты говорят, что особого смысла в этом нет. Возможно, что-то я не так делаю, но ... В пределах того, что я делаю, мне "за глаза" хватает того, что есть. А там время покажет... Но потоки прошу даже не рекламировать :)

Сейчас же мне, например, больше интерсно и актуально, как реализовать виджеты Qt не в основном потоке. "Мечта должна быть бескрылой и приземленной"... :)

Что вы имеете ввиду под "параллельностью"? Одновременное выполнение? Если она у вас уже есть, тогда зачем вам потоки? А если, как я понял, у вас "автоматы в одном потоке", то нет у вас ни какой параллельности - у вас конкурентное выполнение (одновременно работает только один кусок кода).

Что вы имеете ввиду под "параллельностью"?

Я бы и Вам задал этот вопрос, но подозреваю, что Вы ответите "стандартно".

Я отвечу так: параллельность - это нечто, что нельзя считать последовательным ;) Поясню на примере. Есть конкретный реальный объект - RS-триггер. Рассчитываю, что Вы не "чистый програмист"и знаете, что это такое. Итак, реальный RS-триггер. Он состоит из двух компонент, которые, как ни крути, параллельно работают. Модели компонент просты, как 5 копеек (нынче - 10 рублей :)) Каждый из них имеет по два входа и одному выходу. Внутри - логическая функция И-НЕ. Между собой они соединены по стандартной схеме.

Далее. У нас есть некая параллельная среда. Она естественным образом реализует некие параллельные вычислительные процессы в том числе может реализовать и наш логический элемент. .Процессы - это алгоритмы, которые реализуются на неком языке програмирования. Заметьте я пишу - некое, некое, некое... Т.е. я не конкретизирую. Я просто описываю некую постановку, ТЗ на реализацию/моделирование реального объекта.

Дальше. Берем какую-то реальную платформу, которую мы считаем параллельной. Пусть это будет упомянутый Вами Rust. У меня ВКПа и ее язык.. Реализуем модель и там и там и сравниваем результвты моделирования с работой реального объекта. Если они совпадают, то то, что мы использовали, можно считать параллельной вычислительной средой. Та среда, в которой результаты другие, какая угодно, но только не параллельная.

Вот и все. Заметьте, я сознательно исключил такие понятия "поток", "одновременность", "конкуррентность", "корутины" и т.д. и т.п. Они придуманы человеком и как-то им трактуются. У нас же есть реальный параллельный и есть (условно) две некие среды, которые претендуют на звание параллельных. И только та среда, в которой модель реализована один в один на структурном уровне и по результатам своей работы совпадает по результатам с работой реального объекта, который без всяких сомнений считается параллельным, то она и считается параллельной. Только так и не иначе.

Но если вдруг результаты двух сред (двух моделей триггера) совпадут, то, поздрвляю, обе среды параллельны! И я сильно удивлюсь, если это так и будет :) Т.е. Rust будет пааллельным.

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

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

Но выше только одна сторона дела. По теории еще должны быть формальные процедуры, которые преобразуют параллельную модель в последовательную и наоборот.

А зачем еще и это? Все тоже просто. Если мы смотрим на тот же RS-триггер, который на уровне "черного ящика" имеет два входа и два выхода, то может ли мы утверждать, что реализован он последовательно или параллельно. Нет, конечно. Это мы может узнать, только заглянув во внутрь его. А там может быть одна компонента или две. В первом случае мы имеем (предположительно) последовательную реализацию, во втором - (уже точно!) параллельную.

Вот как-то так. Вроде все просто как 10 рублей (инфляция, однако), но одновременно и сложно, т.к. доходит эта "простота" не сразу и не до всех ;).

Получается у вас научно-аналитический интерес? Т.е. не выполнять какую-то задачу быстро за счёт распараллеливания, а реализовать модель чего-то из "реального мира". И главное что бы она выдавала результат правильный, а скорость - второе дело.

Почти так, да не так :-)

"Модель" нужна чтобы время от времени тестировать среду, в которой работаю. Я ж вношу в нее изменения, а любое изменение, если Вы программист, влечет за собой сами знаете что. А проверенная среда создает увереность в работе и, главное, создает гарантии правильной работы. А в работе главное создавать правильные, а не быстрые решения. Быстрое, но неправильное решение - бессмысленно, если не опасно.

Но и про скорость не забываю. Ведь вносимые изменение - это часто в целях ускорения работы среды. Дискретное время процесса от 1 мсек это, наверное, о чем-то да говорит? Попытка с потоками тоже была, чтобы еще быстрее работало. Но нужно, чтобы и правильно! А получилось, может, и быстрее, но не правильно...

Пришлось откатить назад, потом тестировать... Короче - геморрой один с этими потоками.получился. Решил пусть пока так, т.е. немного медленнее (как было), но зато правильно. Лучшее, порой, враг хорошего. Это не надо забывать..

А "научно-аналитический интерес" - это я люблю. Тут Вы прямо в точку. Но за него не платят, к сожалению. Считается, что он создается сам собой. Вот и создаем по ходу дела, так сказать... :)

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

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

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

С таким подходом можно получить выигрыш от структурного распараллеливания.

С таким подходом можно получить выигрыш от структурного распараллеливания.

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

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

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

Я бы сказал даже так - совсем не укладывается. Если бы укладывалось, то ядро работало бы, думаю минимум на два порядка быстрее. И если сейчас, например, жесткий дискретный такт - 1 мсек на обычной винде(было бы интересно знать, какое дискретное время процессов у специализированных систем жесткого реального времени), то было бы, соотвественно, 0.01 мсек. Но, думаю, в подавляющем большинстве случае достаточно уже того, что есть. По крайней мере, в моей практике.

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

Но еще раз. Вы все время тянете меня в сторону потоков. И не пытайтесь... ;) Как вычислительная модель, они совершенно бесполезны для параллельных вычислений. Только муть наводят. Я же говорю про нормальные, правильные параллельные вычисления, а не про те, про которые Вы говорите и где потоки эффективны - независимые или слабо взаимодействующие процессы. Таких мало. Нормальная параллельная задача - это не такое уж большое число небольших, но сильно взаимодействующих между собой процессов. В автоматном программирования - это сеть автоматов. Здесь потоки сильно проседают и начинаю даже, порой, проигрывать однопоточной реализации автоматного параллелизма.

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

В Qt так же как и везде, как минимум с появления Win32. Основной поток программы обрабатывает события от операционной системы (вернее от системы рабочего стола, но в винде эта штука встроенная). И если ваш "медленный" код не даёт выполняться этой обработке, то у вас "зависает" UI.
Что бы такого не было, надо либо периодически прерывать свой код на вызов функции обработки событий. Либо выносить свой медленный код в отдельный поток.

Именно что свой код в отдельный поток, а не QT. Так будет проще.

Sign up to leave a comment.

Articles