|
|||||||
Qt и многопоточность: Пример потомка ExtThread и локальная память потоков
Время создания: 23.03.2023 16:41
Автор: Андрей Боровский
Текстовые метки: c++, qt, поток, многопоточность, QThread
Раздел: Компьютер - Программирование - Язык C++ (Си++) - Библиотека Qt - Многопоточность
Запись: xintrea/mytetra_syncro/master/base/1679578903ybrti17u6q/text.html на raw.github.com
|
|||||||
|
|||||||
Мы используем класс ExtThread, чтобы создать в демонстрационной программе поток, сканирующий директории, начиная с заданной, в поисках файла с именем, соответствующим маске. Наша процедура сканирует все директории и выдает имена всех подходящих файлов. Для того чтобы написать собственную процедуру потока, как и в случае с QThread , мы создаем класс-потомок ExtThread, в котором перекрываем метод run(). Этот класс называется FileFinder (листинг 5.11). Листинг 5.11. Класс FileFinder class FileFinder : public ExtThread { Q_OBJECT public: explicit FileFinder(QObject *parent = 0); ~FileFinder(); void findFiles(const QString &startFrom, const QString &filter);
signals: void updateList(QString str); public slots: protected:
void run();
private: QString startFrom; QString filter; }; Помимо перекрытого метода run() класс FileFinder имеет метод findFiles(), который позволяет задать начальную директорию для поиска и маску имени файла. После вызова метода findFiles() объект класса FileFinder начинает посылать сигналы updateList(), которые передают параметр типа QString. В параметре содержится список полных имен файлов, найденных с момента отправки предыдущего сигнала updateList(). Имена файлов разделены символами \n. Думаю, внешний интерфейс класса FileFinder не вызывает у вас вопросов. Перейдем к реализации (листинг 5.12). Листинг 5.12. Реализация класса FileFinder FileFinder::FileFinder(QObject *parent) : ExtThread(parent) { } FileFinder::~FileFinder() { } void FileFinder::run() { QStringList results; QDir dir(startFrom, filter, QDir::SortFlags( QDir::Name | QDir::IgnoreCase), QDir::AllDirs | QDir::Files); QFileInfoList fil;
for (int i = 0; i < dir.entryInfoList().count(); i++) { if (dir.entryInfoList().at(i).isFile()) results.append(dir.entryInfoList().at(i).absoluteFilePath()); if (dir.entryInfoList().at(i).isDir()) if ((dir.entryInfoList().at(i).fileName() != "." ) && (dir.entryInfoList().at(i).fileName() != "..")) fil.append(dir.entryInfoList().at(i)); }
QString str; for (int k = 0; k < results.count(); k++) str = str + results.at(k) + "\n"; emit updateList(str);
results.clear();
if (CancellationPoint()) return;
while (fil.count() != 0) { for (int i = fil.count() — 1; i >= 0 ; i--) { QDir dir(fil.at(i).absoluteFilePath(), filter, QDir::SortFlags( QDir::Name | QDir::IgnoreCase), QDir::AllDirs|QDir::Files); fil.removeAt(i); for (int j = 0; j < dir.entryInfoList().count(); j++) { if (dir.entryInfoList().at(j).isDir()) { if ((dir.entryInfoList().at(j).fileName() != ".") && (dir.entryInfoList().at(j).fileName() != "..")) fil.append(dir.entryInfoList().at(j)); } else results.append(dir.entryInfoList().at(j). absoluteFilePath()); }
if (results.count() > 0) { QString str; for (int k = 0; k < results.count(); k++) str = str + results.at(k) + "\n"; emit updateList(str);
results.clear(); }
if (CancellationPoint()) return; }
if (CancellationPoint()) return; } done(); } void FileFinder::findFiles(const QString &startFrom, const QString &filter) { this->startFrom = startFrom; this->filter = filter;
start(); } Метод findFiles() заполняет поля класса FileFinder и вызывает метод start() своего базового класса, т. е. потока ExtThread. В методе run() реализована нерекурсивная процедура обхода дерева директорий, начиная c заданной. Вдаваться в детали мы не будем. Нетрудно заметить, что в теле процедуры мы периодически эмитируем сигнал updateList() и вызываем метод CancellationPoint(). ПРИМЕЧАНИЕ Одна из причин, по которой я отказался от рекурсивной функции обхода дерева директорий, заключается в необходимости корректного выхода из потока при вызове функции CancellationPoint(). Во всех операционных системах, поддерживаемых Qt, есть функция ExitThread() (или ей подобная), которая может завершить данный поток, будучи вызвана из функции любой глубины вложенности. Но при программировании в С++ эта функция неприменима. Вот почему на всем протяжении метода run() мы должны иметь возможность вызвать return непосредственно из метода run(). Впрочем, это ограничение не является существенным, ведь теория учит нас, что любая рекурсивная функция может быть преобразована в нерекурсивную. Подключить наш класс FileFinder к графическому интерфейсу приложения очень просто (листинг 5.13). Листинг 5.13. Создание графического интерфейса для FileFinder Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog) { ui->setupUi(this); fileFinder = new FileFinder();
paused = false; searching = false;
connect(fileFinder, SIGNAL(finished()), this, SLOT(serachFinished()));
connect(fileFinder, SIGNAL(updateList(QString)), this, SLOT(updateList(QString)));
counter = 0; } Здесь мы связываем два сигнала объекта fileFinder (унаследованный finished() и собственный updateList() ) со слотами класса Dialog. Теперь у нас есть собственный класс для поиска файлов (рис. 5.3). Рис. 5.3. Поиск файлов с помощью многопоточного приложения ПРИМЕЧАНИЕ При тестировании программы под ОС Linux помните, что благодаря ссылкам в файловых системах Linux могут появиться циклы. То есть структура файловой системы Linux представляет собой не дерево, а граф в более общем смысле. Поиск циклов на графе — задача увлекательная, но мы ею не занимаемся. Бесконечного зацикливания нашей функции на циклах графа все равно не произойдет, т. к. поиск файла по конкретному пути завершится, когда суммарная длина пути превысит максимальную длину имени файла. В связи с рассмотренным примером возникает один весьма важный вопрос: что делать, если поток A посылает сигналы потоку B быстрее, чем поток B успевает их обрабатывать? Мы знаем, что это приведет к резкому замедлению или даже полному "зависанию" программы. В нашем примере поток fileFinder выполняет больше работы, чем основной поток и потому основной поток успевает обрабатывать сигналы, которые посылает fileFinder. Так, по крайней мере, обстоит дело в тех системах, в которых пример тестировался. Но мы помним, что, вообще говоря, мы не должны делать никаких предположений относительно сравнительной скорости выполнения потоков, и нам необходимы механизмы, которые бы гарантировали, что сигналы от потока-источника не будут поступать слишком часто. Один из таких механизмов — метод blockSignals(), определенный в классе QObject. Если наш главный поток вызовет метод fileFinder->blockSignals(true), сигналы от потока fileFinder перестанут поступать до тех пор, пока главный поток не выполнит вызов fileFinder->blockSignals(false). При этом поток fileFinder ничего не заметит, в том смысле, что его операции не будут приостановлены. Глядя на исходный текст fileFinder, нетрудно убедиться, что даже при краткосрочном блокировании сигналов приложение может пропустить часть найденных файлов. Объект может проверить, заблокированы ли его сигналы, с помощью метода signalsBlocked(). Если этот механизм используется, мы должны строить работу объекта, исходя из предположения, что не все его сигналы достигнут адресата. ПРИМЕЧАНИЕ Другое распространенное применение метода blockSignals() заставить объект прекратить рассылку сигналов при завершении его жизненного цикла. Еще один способ решения той же проблемы — передать константу Qt::Blocking QueuedConnection в последнем аргументе метода connect() при связывании сигнала одного потока со слотом другого. В результате поток, пославший сигнал, будет заблокирован до тех пор, пока слот потока-приемника не завершит обработку этого сигнала. Это довольно специфический метод связывания сигналов и слотов. Он подходит для тех случаев, когда один поток посылает сигналы другому потоку нечасто, и при этом поток-источник должен точно знать, когда обработка этих сигналов закончится (этот способ, между прочим, можно использовать для разделения доступа потоков к критическим ресурсам). Однако этот способ сильно тормозит работу потока-источника сигналов в том случае, если сигналы посылаются часто (в предельном случае два потока могут выполнять операцию медленнее, чем это сделал бы один поток!). Кроме того, такой подход чреват взаимоблокировками. Локальная память потоков Давайте вспомним, что кому принадлежит. Объект потока и все его данные принадлежат тому потоку, который создал этот объект. Все переменные, созданные в стеке метода run(), принадлежат потоку, который инкапсулирован в классе потока. Что касается данных этого класса, то они, в принципе, доступны всем потокам, т. к. память потоков — это общая память приложения. Поскольку другие классы не имеют доступа к структурам данных, объявленным в разделе private: класса потока, и эти структуры создаются в конструкторе класса для каждого объекта потока, фактически эти структуры данных являются собственностью конкретного потока. Но иногда потоку требуется иметь собственные данные, отделенные на более глубоком уровне (на уровне компилятора или операционной системы, т. к. архитектуры современных процессоров не защищают области памяти потоков друг от друга). Qt позволяет вам использовать локальную память потока, правда, пользы от этого немного. Класс QThreadStorage, который управляет локальной памятью потоков, позволяет вам хранить только один указатель. Причем содержимое этого указателя должно быть либо нулем, либо адресом памяти, выделенным с помощью new. Класс QThreadStorage берет на себя заботу о жизненном цикле этого указателя и уничтожает соответствующий объект оператором delete при завершении процедуры потока с помощью оператора return. Вообще говоря, локальная память потоков важна при программировании на языке C и его потомках. В объектно-ориентированном мире C++ возможность связывать данные с потоком именно таким способом не имеет особого смысла. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|