1 / 38

Qt – Chapter 18: Multithreading

Qt – Chapter 18: Multithreading. Tworzenie nowego wątku polega na utworzeniu klasy dziedziczącej po QThread i nadpisanie w niej metody run(). class Thread : public QThread { Q_OBJECT public: Thread(); void setMessage(const QString &message); void stop(); protected: void run();

harvey
Download Presentation

Qt – Chapter 18: Multithreading

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Qt – Chapter 18: Multithreading

  2. Tworzenie nowego wątku polega na utworzeniu klasy dziedziczącej po QThread i nadpisanie w niej metody run(). class Thread : public QThread { Q_OBJECT public: Thread(); void setMessage(const QString &message); void stop(); protected: void run(); private: QString messageStr; volatile bool stopped; }; Tworzenie wątków

  3. Thread::Thread() { stopped = false; } void Thread::run() { while (!stopped) cerr << qPrintable(messageStr); stopped = false; cerr << endl; } void Thread::stop() { stopped = true; }

  4. Tworzenie prostej aplikacji uruchamiającej wątki. class ThreadDialog : public QDialog { Q_OBJECT public: ThreadDialog(QWidget *parent = 0); protected: void closeEvent(QCloseEvent *event); private slots: void startOrStopThreadA(); void startOrStopThreadB(); private: Thread threadA; Thread threadB; QPushButton *threadAButton; QPushButton *threadBButton; QPushButton *quitButton; };

  5. ThreadDialog::ThreadDialog(QWidget *parent) : QDialog(parent) { threadA.setMessage("A"); threadB.setMessage("B"); threadAButton = new QPushButton(tr("Start A")); threadBButton = new QPushButton(tr("Start B")); quitButton = new QPushButton(tr("Quit")); quitButton->setDefault(true); connect(threadAButton, SIGNAL(clicked()), this, SLOT(startOrStopThreadA())); connect(threadBButton, SIGNAL(clicked()), this, SLOT(startOrStopThreadB())); }

  6. void ThreadDialog::startOrStopThreadA() { if (threadA.isRunning()) { threadA.stop(); threadAButton->setText(tr("Start A")); } else { threadA.start(); threadAButton->setText(tr("Stop A")); } } void ThreadDialog::startOrStopThreadB() { if (threadB.isRunning()) { threadB.stop(); threadBButton->setText(tr("Start B")); } else { threadB.start(); threadBButton->setText(tr("Stop B")); } }

  7. void ThreadDialog::closeEvent(QCloseEvent *event) { threadA.stop(); threadB.stop(); threadA.wait(); threadB.wait(); event->accept(); } Przykładowe działanie: AAAAABBBBBAAAAABBBBBABABABBBBBBBBBBBBBBBBAAAAAAAAAAA...

  8. QMutex QReadWriteLock QSemaphore QWaitCondition Synchronizacja

  9. Obiekty klasy QMutex służą do zablokowania dostępu do danych lub części kodu w momencie gdy tylko jeden wątek może mieć do nich dostęp w tym samym czasie, innymi słowy służy do organizowania sekcji krytycznych. Obiekty typu QMutex posiadają metody: lock() - blokowanie muteksu, ew. jeśli obiekt jest już zablokowany czekanie aż zostanie zwolniony i będzie można go zablokować tryLock() - próba zablokowania muteksu, zwraca true jeśli się udało lub false jeśli obiekt już jest zablokowany unlock() - odblokowanie obiektu QMutex

  10. Modyfikując poprzedni przykład: class Thread : public QThread { ... private: ... QMutex mutex; }; void Thread::stop() { mutex.lock(); stopped = true; mutex.unlock(); }

  11. void Thread::run() { forever { mutex.lock(); if (stopped) { stopped = false; mutex.unlock(); break; } mutex.unlock(); cerr << qPrintable(messageStr); } cerr << endl; }

  12. Jednak aby nie trzeba było pamiętać o odblokowywaniu muteksu wygodniej jest użyć obiektów klasy QMutexLocker. void Thread::run() { forever { { QMutexLocker locker(&mutex); if (stopped) { stopped = false; break; } } cerr << qPrintable(messageStr); } cerr << endl; } void Thread::stop() { QMutexLocker locker(&mutex); stopped = true; }

  13. Obiekty typu QMutex umożliwiają blokowanie obiektów tylko dla jednego wątku. Jeśli chcemy zablokować jakiś obiekt do jednoczesnego czytania dla wielu wątków możemy użyć klasy QReadWriteLock. QReadWriteLock

  14. MyData data; QReadWriteLock lock; void ReaderThread::run() { ... lock.lockForRead(); dostep_do_danej_bez_jej_modyfikacji(&data); lock.unlock(); ... } void WriterThread::run() { ... lock.lockForWrite(); modyfikacja_danych(&data); lock.unlock(); ... }

  15. Analogicznie do QMutexLocker istnieją również klasy QReadLocker i QWriteLocker służące do blokowania obiektów klasy QReadWriteLock odpowiednio do odczytu i do zapisu.

  16. QSemaphore jest kolejną klasą służącą do blokowania dostępu do danych dla wielu wątków. W odróżnieniu od poprzednich w semaforach można ustawi liczbę obiektów jakie mogą jednocześnie być udostępnione dla wątków. QSemaphore semaphore(1); | QMutex mutex; semaphore.acquire(); | mutex.lock(); semaphore.release(); | mutex.unlock(); Semafory najczęściej są wykorzystywane do zażądzania dostępem do wielu danych np. problem Producent-Konsumer. QSemaphore

  17. Problem Producent-Konsumer: const int DataSize = 10; const int BufferSize = 4; int buffer[BufferSize]; Wątek Producer cyklicznie wypełnia tablice buffer, natomiast wątek Consumer odczytuje wpisane znaki.

  18. QSemaphore freeSpace(BufferSize); QSemaphore usedSpace(0); void Producer::run() { for (int i = 0; i < DataSize; ++i) { freeSpace.acquire(); buffer[i % BufferSize]=i; cerr << ”Producer zapisal: ” << i << endl; usedSpace.release(); } } void Consumer::run() { for (int i = 0; i < DataSize; ++i) { usedSpace.acquire(); cerr << ”Consumer odczytal: ” << buffer[i % BufferSize] << endl; freeSpace.release(); } cerr << endl; }

  19. int main() { Producer producer; Consumer consumer; producer.start(); consumer.start(); producer.wait(); consumer.wait(); return 0; }

  20. Przykładowe działanie programu: Producer zapisal: 0 Consumer odczytal: 0 Producer zapisal: 1 Consumer odczytal: 1 Producer zapisal: 2 Consumer odczytal: 2 Producer zapisal: 3 Consumer odczytal: 3 ... lub: Producer zapisal: 0 Producer zapisal: 1 Producer zapisal: 2 Producer zapisal: 3 Consumer odczytal: 0 Consumer odczytal: 1 Consumer odczytal: 2 Consumer odczytal: 3 Producer zapisal: 4 ...

  21. Kolejną klasą umożliwaijącą synchronizację wątków jest QWaitCondition. Obiekty tej klasy umożliwiają pobudzanie innych wątków przy spełnieniu określonych warunków. Aby przybliżyć sposób jej działania zmodyfikujmy kod z programu Producent-Konsument. const int DataSize = 10; const int BufferSize = 4; int buffer[BufferSize]; QWaitCondition bufferIsNotFull; QWaitCondition bufferIsNotEmpty; QMutex mutex; int usedSpace = 0; QWaitCondition

  22. void Producer::run() { for (int i = 0; i < DataSize; ++i) { mutex.lock(); while (usedSpace == BufferSize) bufferIsNotFull.wait(&mutex); buffer[i % BufferSize] = i; ++usedSpace; bufferIsNotEmpty.wakeAll(); mutex.unlock(); } } Jeśli mamy tylko jednego producenta pętle while można zamienić na warunek if. if (usedSpace == BufferSize) { mutex.unlock(); bufferIsNotFull.wait(); mutex.lock(); }

  23. void Consumer::run() { for (int i = 0; i < DataSize; ++i) { mutex.lock(); while (usedSpace == 0) bufferIsNotEmpty.wait(&mutex); cerr << buffer[i % BufferSize]; --usedSpace; bufferIsNotFull.wakeAll(); mutex.unlock(); } cerr << endl; }

  24. Wszystkie wątki w jednym programie mają wspólną przestrzeń adresową, dlatego mogą korzystać z tych samych zmiennych globalnych. Czasem jednak przydatne jest posiadanie kopii niektórych danych osobno dla każdego wątku. Do tego celu służy obszar danych zwany thread-local storage (TLS) lub thread-specific data. Zmienne przechowywane w tym obszarze są duplikowane dla każdego wątku i każdy wątek może je modyfikować bez wpływu na pozostałe wątki. Thread-local storage

  25. W Qt do organizacji obszaru TLS może służyć klasa QThreadStorage<T> QThreadStorage<QHash<int, double> *> cache; void insertIntoCache(int id, double value) { if (!cache.hasLocalData()) cache.setLocalData(new QHash<int, double>); cache.localData()->insert(id, value); } void removeFromCache(int id) { if (cache.hasLocalData()) cache.localData()->remove(id); } QThreadStorage<T>

  26. Komunikacji wątku głównego z innym wątkiem powinna odbywać się przy wykorzystaniu sygnałów i slotów tak aby uniknąć blokowania interfejsu użytkownika. Mechanizm ten zostanie przedstawiony na przykładzie aplikacji Image Pro. Komunikacja z głównym wątkiem

  27. ImageWindow::ImageWindow() { imageLabel = new QLabel; imageLabel->setBackgroundRole(QPalette::Dark); imageLabel->setAutoFillBackground(true); imageLabel->setAlignment(Qt::AlignLeft| Qt::AlignTop); setCentralWidget(imageLabel); createActions(); createMenus(); statusBar()->showMessage(tr("Ready"), 2000); connect(&thread, SIGNAL(transactionStarted( const QString &)), statusBar(), SLOT(showMessage(const QString &))); connect(&thread, SIGNAL(finished()), this, SLOT(allTransactionsDone())); setCurrentFile(""); }

  28. void ImageWindow::flipHorizontally() { addTransaction(new FlipTransaction(Qt::Horizontal)); } void ImageWindow::addTransaction( Transaction *transact) { thread.addTransaction(transact); openAction->setEnabled(false); saveAction->setEnabled(false); saveAsAction->setEnabled(false); }

  29. void ImageWindow::allTransactionsDone() { openAction->setEnabled(true); saveAction->setEnabled(true); saveAsAction->setEnabled(true); imageLabel>setPixmap(QPixmap::fromImage( thread.image())); setWindowModified(true); statusBar()->showMessage(tr("Ready"), 2000); }

  30. class TransactionThread : public QThread { Q_OBJECT public: void addTransaction(Transaction *transact); void setImage(const QImage &image); QImage image(); signals: void transactionStarted( const QString &message); protected: void run(); private: QMutex mutex; QImage currentImage; QQueue<Transaction *> transactions; };

  31. void TransactionThread::addTransaction( Transaction *transact) { QMutexLocker locker(&mutex); transactions.enqueue(transact); if (!isRunning()) start(); } void TransactionThread::setImage(const QImage &image) { QMutexLocker locker(&mutex); currentImage = image; } QImage TransactionThread::image() { QMutexLocker locker(&mutex); return currentImage; }

  32. void TransactionThread::run() { Transaction *transact; forever { mutex.lock(); if (transactions.isEmpty()) { mutex.unlock(); break; } QImage oldImage = currentImage; transact = transactions.dequeue(); mutex.unlock(); emit transactionStarted(transact->message()); QImage newImage = transact->apply(oldImage); delete transact; mutex.lock(); currentImage = newImage; mutex.unlock(); } }

  33. class Transaction { public: virtual ~Transaction() { } virtual QImage apply(const QImage &image) = 0; virtual QString message() = 0; }; class FlipTransaction : public Transaction { public: FlipTransaction(Qt::Orientation orientation); QImage apply(const QImage &image); QString message(); private: Qt::Orientation orientation; };

  34. QImage FlipTransaction::apply(const QImage &image) { return image.mirrored( orientation == Qt::Horizontal, orientation == Qt::Vertical); } QString FlipTransaction::message() { if (orientation == Qt::Horizontal) { return QObject::tr( "Flipping image horizontally..."); } else { return QObject::tr( "Flipping image vertically..."); } }

  35. Ze względu na współbieżność klasy możemy podzielić na: 1) thread-safe – wszystkie metody klasy mogą być wywołane jednocześnie z różnych wątków bez ingerencji ze sobą, nawet wtedy gdy działają na tym samym obiekcie 2) reentrant – różne instancje tego samego obiektu mogą być używane jednocześnie w różnych wątkach Bezpieczeństwo

  36. QObject należy do klas typu reentrant przy czym należy pamiętać o kilku rzeczach: - dzieci klasy QObject muszą być utworzone w ich ojcowskim wątku - należy usuwać wszystkie obiekty QObject utworzone w danym wątku przed jego usunięciem - obiekty QObject muszą być usuwane w wątku, który je stworzył (jeśli nie jest to możliwe należy użyć funkcji QObject::deleteLater()

  37. Komunikacja z obiektami klas, które nie są reentrant musi odbywać się za pomocą mechanizmu slotów i sygnałów lub poprzez wywołanie funkcji QMetaObject::invokeMethod(). void MyThread::run() { ... QMetaObject::invokeMethod(label, SLOT(setText(const QString &)), Q_ARG(QString, "Hello")); ... }

  38. Koniec

More Related