Как стать автором
Обновить

Комментарии 28

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

Переопределив метод QThread::run(), знайте, что все объекты, созданные в нем, будут недоступны из вне, т.к. созданы в стеке метода run() (хотя и это можно обойти).
Поясните эту фразу. Во-первых, если объект создан через new, он будет помещен в heap, а не стек. Во-вторых, если объект создан на стеке, он будет на том же самом стеке независимо от того, переопределили вы QThread::run() или используете moveToThread. В-третьих, независимо от того, где создан объект, указатель на него можно переместить в любой другой поток (будет ли это безопасно — другой вопрос). Непонятно, о каком «из вне» идет речь.

Теперь про завершение потоков. Если в потоке произойдет segfault или событие аналогичной катастрофичности, то упадет весь процесс целиком, и сделать тут мало что можно. А чтобы справиться с неотловленными исключениями, необязательно использовать обертку для QThread. Достаточно в QThread::run сделать безусловный try/catch. Точка выхода из потока тут тоже одна — это завершение метода QThread::run. После этого Qt самостоятельно завершит поток.

И на всякий случай замечу, что правильно zero, а не zerro.
Спасибо за обстоятельный комментарий.
По первому абзацу отвечу, что цель статьи — показать возможность применения конечных автоматов для управления жизненным циклом потока.
Важна была идея. Возможно, кто-то увидел для себя решение.
Например, Вы комментарием подсказали читателям направление более основательно изучения всех трёх составляющих, и это — здорово!
Работу с libusb выбрал как пример, на котором проверил материал.
Так же эту технологи. использую для себя при сканировании каталогов на обновление файловой системы, применяю при работе с подключениями к QDatabase.

С фразой получилось «корявенько». Для объектов стека метода run() не имеет смысла применение moveToThread(), т.к. это — переменная метода (возможно, я чего, то не понял).
Указатели нельзя физически (если, конечно, не применить memmove()) переместить в другой поток: память у потоков общая, да и не имеет смысла. Вроде, это видно из исходников QT. Речь идёт о смене владельца указателя.

По третьему пункту: Thread_helper позволяет удалить объект потока простым delete вызвав дестуктор объекта потока и остановив конечный автомат. В коде деструктора класса потока есть quit() и т.д.

Спасибо за поправку к исключениям. Буду думать…
Для объектов стека метода run() не имеет смысла применение moveToThread(), т.к. это — переменная метода (возможно, я чего, то не понял).
Для объектов, созданных в некотором потоке, не имеет смысл вызывать moveToThread, потому что они по умолчанию уже принадлежат этому потоку. Кроме того, если вы переопределяете QThread::run и не запускаете event loop (а в реализации по умолчанию он запускается), то moveToThread теряет смысл, потому что единственное, на что он влияет, — в каком потоке будут вызываться обработчики событий и слоты объекта, а при отсутствии event loop в потоке они вызываться не будут вообще.

Указатели нельзя физически (если, конечно, не применить memmove()) переместить в другой поток: память у потоков общая, да и не имеет смысла. Вроде, это видно из исходников QT. Речь идёт о смене владельца указателя.
Указатель — это просто число, и его легко скопировать в другой поток. Затем можно использовать этот объект одновременно или по очереди в двух потоках. И это в общем случае не зависит от того, где и как объект создан.

Понятие владельца весьма расплывчато. Parent object в Qt — это другой объект, который удалит наш объект вместе с собой. Thread affinity, как я уже сказал, определяет, в каком потоке обрабатываются события объекта. И ни один из этих параметров не запрещает использовать любой объект в любом другом потоке в то же время. Правда, для безопасного использования желательно, чтобы объект был thread-safe и его никто не удалил в процессе обработки.
Для объектов, созданных в некотором потоке, не имеет смысл вызывать moveToThread, потому что они по умолчанию уже принадлежат этому потоку. Кроме того, если вы переопределяете QThread::run и не запускаете event loop (а в реализации по умолчанию он запускается), то moveToThread теряет смысл, потому что единственное, на что он влияет, — в каком потоке будут вызываться обработчики событий и слоты объекта, а при отсутствии event loop в потоке они вызываться не будут вообще.

Вроде, это и так понятно… Всё таки, плохо я излагаю мысли. Слава богу, что пишу редко :).

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

А вот для реализации подобного совершенно не нужны такие тяжеловесные вещи как Qt. Вполне достаточно поддержки потоков реализованных в стандартной библиотеке языка (std::thread и т.п.). Ну а в качестве конечного автомата, как уже было указано, вполне логично взять boost.msm.
Я обращался статьёй к Qt-эшникам. В самом начале упомянул о boost. К тому же, есть у меня собственные наработки на ANSI C с использованием pthread функций. Вариантов — куча, умных и талантливых людей — и подавно. Статься создана для одной веви разработок — Qt Framework. Хотелось донести идею: вдруг кто-то найдёт для себя какое-то новое решение.
Дело не в этом. Просто Qt — это очень тяжёлый фреймворк с довольно устаревшей архитектурой. Так что его имеет смысл использовать, только если нет другой альтернативы. Такая область есть и довольно широкая — написание кроссплатформенного GUI (собственно Qt практически единственная в мире библиотека, обеспечивающая написание GUI на все значимые платформы из одной кодовой базы). Так что приходится частенько использовать этого монстра. Однако в вашем случае не видно не то что требования на полную кроссплатформенность GUI, но даже вообще требования на наличие GUI. Соответственно если таких ограничений нет, то можно написать на порядок более изящную (используя современный C++) и лёгкую (сколько мегабайт занимает ваш примерчик и сколько времени он собирается?) программу.
Требования кросcплатформенности есть, но я не могу тащить всю свою библиотеку (libFWGUI, libFWDtatbase, libFWCore...)! :)
Повторю, статья для тех, кто работает, именно, с Qt.
Так требуется просто кроссплатформенность (это как бы по умолчанию у C++ и boost'a) или кроссплатформенность GUI? В вашей статье я вообще не нашёл упоминания, что данная программка реализует GUI… )))

Ну и даже если говорить о кроссплатформенном GUI, то надо точно определиться с набором требуемых плаформ, Потому как достаточно исключить из полного списка актуальных ОС (Windows, Android, iOS, OSX, Linux) хотя бы одну (скажем Android), как возникнет множество альтернатив. На порядок более лёгких и современных. Причём при реализации через обычные потоки и boost обсуждаемый код (многопоточная работа с usb с помощью конечного автомата) не будет зависть от выбора GUI библиотеки. )

Т.е. лично моя позиция — даже если нам приходится использовать Qt для GUI, то только для этого его и надо применять, а всё остальное реализовывать независимыми средствами.
Было бы неплохо, если бы вы об этом создали статью. Я бы добавил в «Избранное». Видел стройное решение по конечным автоматам на google-разработка на boost.
Кстати, в сети очень мало и разрозненно упоминается работа с boost применительно к каким-либо технологиям. Для начинающих было бы очень полезно!
Т.е. лично моя позиция — даже если нам приходится использовать Qt для GUI, то только для этого его и надо применять, а всё остальное реализовывать независимыми средствами.

Таким образом, вам придётся набирать несколько сотрудников (вместо одного Qt-эшника) для реализации проекта. Современные IT-технологии позволяют «расслабиться» в плане ресурсов и скорости. Это — «минус». «Плюс» — единая платформа разработки и снижение себестоимости проекта.
Это — так, размышления. :)
Т.е. кросплатформенные технологии как межпроцессное взаимодействие и работа с процессами, работа с БД, кооперативная многозадачность практически из коробки, работа с последовательными портами, bluetooth, nfc, сетью включая клиент серверную логику и websocket, несколько паттернов работы с потоками включая map-reduce и различные примитивы синхронизации включая атомарные, implicit sharing контейнерные классы, работа с файловой системой включая опевещения изменения состояния файла, работа с форматами json, xml, регулярные выражения, unicode строки из коробки, средства для юнит тестирования, средства для встраивания скриптового кода в бинарные программы ну и наконец самые распространенные сигнал слоты заменяющие массу кода реализации их вручную с поддержкой очередей — все это будете тягать не из Qt несмотря на то, что он уже есть у вас в проекте?
Если отвечать кратко, то да. Потому что в большинстве случаев эти реализации будут на порядок качественнее и мощнее реализации из Qt.

Но фраза
все это будете тягать не из Qt несмотря на то, что он уже есть у вас в проекте?
на практике абсолютно не корректна. Потому что "уже в проекте" у меня обычно подключён Boost (стандарт де-факто в мире C++), причём ещё до всяких Qt и т.п. (т.к. используется и для не GUI приложений). Так вот Boost покрывает 90% описанных выше не GUI возможностей, причём с гораздо более высоким качеством. Оставшиеся мелочи типа работы с БД или NFC уже нужны в редких случаях. Ну а если уж и нужны, то тогда подключаются как отдельные библиотеки совсем другого уровня (типа той же sqlpp11, проверяющей sql синтаксис на этапе компиляции) чем в Qt.

Однако надо признать, что и в Qt есть несколько полезных мелочей не из области GUI, которые я использую. И об отсутствие которых в случае консольных приложений (которые естественно без Qt) соответственно сожалею. Например это удобный класс QSettings, который позволяет удобно и кроссплатформенно хранить настройки. В Boost'е аналогом может служить PropertyTree, но там это несколько менее удобно. Потом в Qt есть ещё удобная система работы с ресурсами, которые компилируются в само приложение и при этом потом доступны через виртуальную файловую систему. Может ещё какие-то мелочи есть, сейчас уже не припомню. )
В итоге получается что если вы уже используете Qt, вы еще дополнительно намешиваете туда солянку из различных либ, которые каждая имеет свои правила использования (один boost чего стоит, абсолютно неоднороден в использовании) и типы, и естественно требуют оверхеда на переконвертацию из одних типов в другие при взаимодействии, при том что в Qt как правило все это уже есть и вы получите однообразный код, который еще, довольно легко встраивать и в многопоточные и событийные паттерны. В вашем же случае, каждая сущность, даже тот же буст в котором этих сущностей очень много, требует своего бережного индивидуального подхода.
Нет, у меня как раз есть ядро приложения, написанное в абсолютно однообразном стиле современного C++ (и да, Boost старается везде выдерживать именно такой стиль). И поверх этого есть GUI нашлёпка написанная на Qt с его уродливым Java стилем. Так вот я могу в любой момент оторвать эту нашлёпку и запустить своё приложение там, где Qt даже запуститься не сможет. ))) Или интегрировать в другой проект со своим GUI (например на базе HTML)..
Я говорил не про стиль кодирования (типа применение с++11 фич), а про способ использования. Какие то либы сами выделяют память и контролируют её, какие то пользуются концепцией RAII, какие то требуют специальных функций для освобождения и создания объектов, какие то не потокобезопасные, какие то наоборот, переусложненные из-за своей безопасности, какие то используют свой асинхронные интерфейс со своим набором коллбэков и правила их задания (потокобезопасность та же, ага). И в конце концов, чтобы общаться с Qt интерфейсом вам весь этот зоопарк типов, коллбеков, менеджеров памяти и прочих с++ крутых вещей нужно приводить к Qt типам и наоборот.
В итоге проект превращается в этакого монстра в котором 80% времени борешься с его очень крутыми фичами и мозголомкой как же мне встроить во все это многообразие новый кусок кода, и только 20% времни тратишь на написание нового функционала.
Я под стилем тоже подразумевал не использование C++11. Я говорил скорее о стиле современного C++, в котором царствует статический полиморфизм, RAII, шаблоны, стековые переменные, элементы функционального стиля и т.п. C++11 с его лямбдами, семантикой перемещения и т.п. всего лишь делает этот подход эффективнее, но он работал и до C++11. В противоположность этому Qt написана в стиле Java (ну или на C++ так тоже писали в 90-ые) со сплошными new, virtual и т.п.

Кстати, забавно, но даже стили именования у Qt и Boost разные. Причём у Boost совпадает со стандартной библиотекой C++, а у Qt совпадает со общепринятом в Java. Хотя это конечно же всё ерунда и дело вкуса, но всё равно показательно. )))

Что же касается приведение к "Qt типам", то это собственно о чём речь? ) У меня в GUI написанном на Qt присутствует ровно один нестандартный (с точки зрения C++) тип данных — это QString. И как раз в нём самом уже реализован нужный механизм конвертации с обычными типами C++. Других "Qt типов" (данных, а не классов виджетов и т.п.) я что-то не припомню.
Я говорил скорее о стиле современного C++, в котором царствует статический полиморфизм, RAII, шаблоны, стековые переменные, элементы функционального стиля и т.п. C++11 с его лямбдами, семантикой перемещения и т.п. всего лишь делает этот подход эффективнее, но он работал и до C++11

Кроме статического полиморфизма, все это широко применяется в Qt.

Qt не написана в стиле Java, Qt взяла от туда некоторое количество подходов и паттернов, включая многопоточное программирование.

Что же касается приведение к «Qt типам», то это собственно о чём речь?

Вы к примеру берете какие либо данные с бд, сначала они приводятся к какому то типу самой либы, которая предоставляет доступ к бд, потом вы из этого извлекаете данные которые приводите к своей сущности, ну а по скольку своя сущность ничего про Qt не знает, вы эту сащность каким либо образом натаскиваете чтобы она могла отобразиться в GUI.
А типов много — классы изображений (QImage, QPixmap), QVariant, контейнеры (QList довольно часто используется в виджетах), QByteArray.
Qt не написана в стиле Java, Qt взяла от туда некоторое количество подходов и паттернов, включая многопоточное программирование.

Непонятно причём тут многопоточное программирование и Java. ) Оно вполне себе существовало ещё до рождения Java. )))

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

Все нормальные C++ библиотеки работают на базе стандартных C++ типов, так же как и моё приложение.

А типов много — классы изображений (QImage, QPixmap), QVariant, контейнеры (QList довольно часто используется в виджетах), QByteArray.

Это всё вполне может использоваться в GUI части приложения. А вот между GUI и основной частью путешествуют только стандартные C++ типы и QString. )
Непонятно причём тут многопоточное программирование и Java. ) Оно вполне себе существовало ещё до рождения Java. )))

Да при том, что это просто еще один способ работы с потоками который никак не противоречит другим способам.

Это всё вполне может использоваться в GUI части приложения. А вот между GUI и основной частью путешествуют только стандартные C++ типы и QString. )

Не важно что путешествует между слоями. Важно какими усилиями вы приводите все к нужному виду.
Не важно что путешествует между слоями. Важно какими усилиями вы приводите все к нужному виду.

В том то и дело, что никаких усилий нет, т.к. перемещаются только базовые типы и QString, в котором конвертация уже встроена.
Кстати, не применить ли вам для своей темы конечные автоматы и потки, скажем, для сканирования каталогов? Автомат отрабатывает эвенты, это может здорово пригодиться.
С libusb много шишек набили, особенно при работе через USB 3.0, особенно на Windows. Если есть возможность заменить работу через какой-то стандартный класс, типа HID (использовать можно через libhid), то лучше использовать его. Либо писать драйвер. Скорее всего косяки в связке WinUSB и libusb. В случае HID будет использоваться драйвер HID.
Без сомнения. К тому же, напрямую работать с драйвером *.sys, зачастую, невозможно, Приходится пользоваться zadig для конвертации.
Однако, многие применяют libusb (исходники библиотеки можно поправить), В статье отражён пример. Плюс, хотелось обратить внимание на работу с контекстом libusb.
исходники библиотеки можно поправить

согласен, но не всегда всё тривиально, тем более на низком уровне, особенно в Windows.

Плюс, хотелось обратить внимание на работу с контекстом libusb.

вообще с контекстом имеет смысл работать во всех случаях, когда у вас программа не только из одной функции main(). Хотя в нашем случае одного контекста на приложение достаточно
Да, задиг — по сути демо для libwdi, пришлось использовать для программы, что бы автоматом устанавливать «драйвер». Хотя есть возможность в устройстве прописать метки WCID, что мы и сделали, но… Или какой-то косяк, или что-то ещё: драйвер ставится, но устройство не отображает своего имени (чего не происходит при установке через Zadig/libwdi) и обратиться через libusb к нему нельзя. Выход: или вручную переставлять драйвер или форсировать через libwdi из своей приложухи (мы сделали второй вариант).

Потом, на USB 3.0 на Win8 (по крайней мере), устройство отображается ДВА раза (у libusb). Первое всегда недоступно (а к нему и происходит обращение при попытке открыть через vid/pid), а второе доступно. Почему так — хз. Плюс косяки с некоторыми конроллерами — устройство видится, но любое обращение к нему, кроме запроса Device descriptor — фейл. Причём часто лечится перетыканием в другой порт того-же контроллера (но не всегда, есть «мёртвые» для libusb контроллеры). При этом, практически не припомню косяков при работе в USB 2.0 моде. Т.е. берём проводок USB 2.0 и втыкаем в тот же порт — всё магическим образом становится прекрасно.
Да. Думаю разделить в дальнейшем релизы доступа для USB на usb_windows.h и usb_unix.h
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории