apeescape2.com
  • メイン
  • プロセスとツール
  • 財務プロセス
  • ブランドデザイン
  • バックエンド
技術

C ++でのQtマルチスレッドに関する欠落している記事

C ++開発者 堅牢なマルチスレッドQtアプリケーションの構築に努めていますが、これらすべての競合状態、同期、デッドロックとライブロックでは、マルチスレッドは決して容易ではありませんでした。あなたの名誉のために、あなたはあきらめず、StackOverflowを精査していることに気づきません。それにもかかわらず、特に各ソリューションには独自の欠点があることを考えると、12の異なる答えから適切で実用的なソリューションを選択することはかなり簡単ではありません。

マルチスレッドは、1つのプロセスのコンテキスト内に複数のスレッドが存在できるようにする、広範なプログラミングおよび実行モデルです。これらのスレッドはプロセスのリソースを共有しますが、独立して実行できます。スレッドプログラミングモデルは、開発者に並行実行の有用な抽象化を提供します。マルチスレッドを1つのプロセスに適用して、マルチプロセッシングシステムでの並列実行を可能にすることもできます。

- ウィキペディア



この記事の目的は、Qtフレームワークを使用した並行プログラミングに関する基本的な知識、特に最も誤解されているトピックを集約することです。読者は、コンテンツを理解するために、QtおよびC ++の以前のバックグラウンドを持っていることが期待されます。

C ++をプログラムする方法

QThreadPoolを使用するかどうかの選択およびQThread

Qtフレームワークは、マルチスレッド用の多くのツールを提供します。適切なツールを選択することは最初は難しい場合がありますが、実際には、決定木は2つのオプションで構成されています。Qtにスレッドを管理させるか、自分でスレッドを管理するかです。ただし、他にも重要な基準があります。

  1. イベントループを必要としないタスク。具体的には、タスクの実行中にシグナル/スロットメカニズムを使用していないタスク。
    使用する: QtConcurrent そして QThreadPool + QRunnable 。

  2. シグナル/スロットを使用するため、イベントループが必要なタスク。
    使用:ワーカーオブジェクトを+に移動 QThread 。

Qtフレームワークの優れた柔軟性により、「イベントループの欠落」の問題を回避し、QRunnableに追加することができます。

class MyTask : public QObject, public QRunnable { Q_OBJECT public: void MyTask::run() { _loop.exec(); } public slots: // you need a signal connected to this slot to exit the loop, // otherwise the thread running the loop would remain blocked... void finishTask() { _loop.exit(); } private: QEventLoop _loop; }

ただし、このような「回避策」は危険で効率的ではないため、回避するようにしてください。シグナルを待機しているためにスレッドプールのスレッドの1つ(MyTaskを実行)がブロックされると、プールから他のタスクを実行できなくなります。

代替テキスト

QThreadを実行することもできますQThread::run()をオーバーライドすることでイベントループなしあなたが何をしているのかを知っている限り、これは完全に問題ありません。たとえば、メソッドquit()を期待しないでくださいそのような場合に働くために。

一度に1つのタスクインスタンスを実行する

一度に1つのタスクインスタンスのみを実行でき、同じタスクを実行するためのすべての保留中の要求が特定のキューで待機していることを確認する必要があるとします。これは、同じファイルへの書き込みやTCPソケットを使用したパケットの送信など、タスクが排他的リソースにアクセスしている場合によく必要になります。

コンピュータサイエンスと生産者/消費者パターンを少し忘れて、些細なことを考えてみましょう。実際のプロジェクトで簡単に見つけることができるもの。

この問題の素朴な解決策は、QMutexを使用することです。タスク関数内では、タスクを実行しようとするすべてのスレッドを効果的にシリアル化するミューテックスを取得するだけで済みます。これにより、一度に1つのスレッドのみが関数を実行できることが保証されます。ただし、このソリューションは、導入することでパフォーマンスに影響を与えます 高い競合 これらのスレッドはすべて、続行する前に(ミューテックスで)ブロックされるため、問題が発生します。このようなタスクを積極的に使用し、その間に何らかの有用なジョブを実行しているスレッドが多数ある場合、これらのスレッドはすべて、ほとんどの時間スリープ状態になります。

void logEvent(const QString & event) { static QMutex lock; QMutexLocker locker(& lock); // high contention! logStream << event; // exclusive resource }

競合を回避するには、キューと、独自のスレッドに存在し、キューを処理するワーカーが必要です。これはほとんど古典的です 生産者/消費者 パターン。労働者 ( 消費者 )キューからリクエストを1つずつ選択し、それぞれが プロデューサー リクエストをキューに追加するだけです。最初は単純に聞こえますが、QQueueの使用を考えるかもしれません。およびQWaitConditionですが、これらのプリミティブなしで目標を達成できるかどうかを確認してみましょう。

初心者のためのモンテカルロシミュレーション
  • QThreadPoolを使用できます保留中のタスクのキューがあるため

または

  • デフォルトのQThread::run()を使用できますQEventLoopがあるからです

最初のオプションはQThreadPoolを使用することです。 QThreadPoolを作成できますインスタンスを作成し、QThreadPool::setMaxThreadCount(1)を使用します。次に、QtConcurrent::run()を使用できますリクエストをスケジュールするには:

class Logger: public QObject { public: explicit Logger(QObject *parent = nullptr) : QObject(parent) { threadPool.setMaxThreadCount(1); } void logEvent(const QString &event) { QtConcurrent::run(&threadPool, [this, event]{ logEventCore(event); }); } private: void logEventCore(const QString &event) { logStream << event; } QThreadPool threadPool; };

このソリューションには1つの利点があります:QThreadPool::clear()すぐにできます キャンセル たとえば、アプリケーションをすばやくシャットダウンする必要がある場合など、保留中のすべてのリクエスト。ただし、に関連する重大な欠点もあります スレッドアフィニティ :logEventCore関数は、呼び出しごとに異なるスレッドで実行される可能性があります。そして、Qtには必要なクラスがいくつかあることを私たちは知っています スレッドアフィニティ :QTimer、QTcpSocketそしておそらく他のいくつか。

Qt仕様がスレッドアフィニティについて述べていること:タイマーは1つのスレッドで開始され、別のスレッドから停止することはできません。また、ソケットインスタンスを所有するスレッドのみがこのソケットを使用できます。これは、タイマーを開始したスレッドで実行中のタイマーをすべて停止し、ソケットを所有するスレッドでQTcpSocket :: close()を呼び出す必要があることを意味します。どちらの例も通常、デストラクタで実行されます。

より良い解決策は、QEventLoopの使用に依存していますQThreadによって提供されます。考え方は単純です。シグナル/スロットメカニズムを使用してリクエストを発行し、スレッド内で実行されているイベントループがキューとして機能し、一度に1つのスロットのみを実行できるようにします。

// the worker that will be moved to a thread class LogWorker: public QObject { Q_OBJECT public: explicit LogWorker(QObject *parent = nullptr); public slots: // this slot will be executed by event loop (one call at a time) void logEvent(const QString &event); };

LogWorkerの実装コンストラクターとlogEventは単純なので、ここでは提供しません。ここで、スレッドとワーカーインスタンスを管理するサービスが必要です。

// interface class LogService : public QObject { Q_OBJECT public: explicit LogService(QObject *parent = nullptr); ~LogService(); signals: // to use the service, just call this signal to send a request: // logService->logEvent('event'); void logEvent(const QString &event); private: QThread *thread; LogWorker *worker; }; // implementation LogService::LogService(QObject *parent) : QObject(parent) { thread = new QThread(this); worker = new LogWorker; worker->moveToThread(thread); connect(this, &LogService::logEvent, worker, &LogWorker::logEvent); connect(thread, &QThread::finished, worker, &QObject::deleteLater); thread->start(); } LogService::~LogService() { thread->quit(); thread->wait(); }

代替テキスト

このコードがどのように機能するかについて説明しましょう。

phpは参照によってオブジェクトを渡します
  • コンストラクターで、スレッドとワーカーのインスタンスを作成します。ワーカーは新しいスレッドに移動されるため、親を受け取らないことに注意してください。このため、Qtはワーカーのメモリを自動的に解放できません。したがって、QThread::finishedを接続してこれを行う必要があります。 deleteLaterへのシグナルスロット。プロキシメソッドも接続しますLogService::logEvent() 〜LogWorker::logEvent() Qt::QueuedConnectionを使用しますスレッドが異なるため、モード。
  • デストラクタにquitを入れますイベントをイベントループのキューに入れます。このイベントは処理されます 後 他のすべてのイベントが処理されます。たとえば、何百ものlogEvent()を作成した場合デストラクタ呼び出しの直前の呼び出しでは、ロガーはquitイベントをフェッチする前にそれらすべてを処理します。もちろん、これには時間がかかるので、wait()する必要があります。イベントループが終了するまで。今後のすべてのロギングリクエストが投稿されたことは言及する価値があります 後 quitイベントは処理されません。
  • ロギング自体(LogWorker::logEvent)は常に同じスレッドで実行されるため、このアプローチは、以下を必要とするクラスでうまく機能します。 スレッドアフィニティ 。同時に、LogWorkerコンストラクタとデストラクタはメインスレッド(具体的にはスレッドLogServiceが実行されている)で実行されるため、そこで実行しているコードには十分注意する必要があります。具体的には、同じスレッドでデストラクタを実行できる場合を除いて、ワーカーのデストラクタでタイマーを停止したり、ソケットを使用したりしないでください。

同じスレッドでワーカーのデストラクタを実行する

ワーカーがタイマーまたはソケットを処理している場合は、デストラクタが同じスレッド(ワーカー用に作成したスレッドとワーカーの移動先)で実行されるようにする必要があります。これをサポートする明白な方法は、サブクラスQThreadです。およびdelete内部の労働者QThread::run()方法。次のテンプレートを検討してください。

template class Thread : QThread { public: explicit Thread(TWorker *worker, QObject *parent = nullptr) : QThread(parent), _worker(worker) { _worker->moveToThread(this); start(); } ~Thread() { quit(); wait(); } TWorker worker() const { return _worker; } protected: void run() override { QThread::run(); delete _worker; } private: TWorker *_worker; };

このテンプレートを使用して、LogServiceを再定義します。前の例から:

// interface class LogService : public Thread { Q_OBJECT public: explicit LogService(QObject *parent = nullptr); signals: void **logEvent**(const QString &event); }; // implementation LogService::**LogService**(QObject *parent) : Thread(new LogWorker, parent) { connect(this, &LogService::logEvent, worker(), &LogWorker::logEvent); }

これがどのように機能するかについて説明しましょう。

  • LogServiceを作成しましたQThreadになるカスタムrun()を実装する必要があるためオブジェクト関数。スレッドのライフサイクルを内部で制御したいので、QThreadの関数にアクセスできないようにプライベートサブクラス化を使用しました。
  • Thread::run()でデフォルトのQThread::run()を呼び出すことでイベントループを実行する関数実装し、イベントループが終了した直後にワーカーインスタンスを破棄します。ワーカーのデストラクタは同じスレッドで実行されることに注意してください。
  • LogService::logEvent()は、ロギングイベントをスレッドのイベントキューに送信するプロキシ関数(シグナル)です。

スレッドの一時停止と再開

もう1つの興味深い機会は、カスタムスレッドを一時停止および再開できることです。アプリケーションが最小化されたり、ロックされたり、ネットワーク接続が失われたりしたときに一時停止する必要のある処理をアプリケーションが実行していると想像してください。これは、ワーカーが再開されるまですべての保留中の要求を保持するカスタム非同期キューを構築することで実現できます。ただし、最も簡単な解決策を探しているので、同じ目的で(再び)イベントループのキューを使用します。

スレッドを一時停止するには、特定の待機条件で待機する必要があることは明らかです。スレッドがこのようにブロックされている場合、そのイベントループはイベントを処理しておらず、Qtはkeepをキューに入れる必要があります。再開されると、イベントループは蓄積されたすべてのリクエストを処理します。待機条件には、単にQWaitConditionを使用します。 QMutexも必要とするオブジェクト。すべてのワーカーが再利用できる汎用ソリューションを設計するには、すべてのサスペンド/レジュームロジックを再利用可能な基本クラスに配置する必要があります。それをSuspendableWorkerと呼びましょう。このようなクラスは、次の2つのメソッドをサポートする必要があります。

  • suspend()待機状態で待機しているスレッドを設定するブロッキング呼び出しになります。これは、一時停止要求をキューに投稿し、それが処理されるまで待機することによって行われます。 QThread::quit()とほとんど同じです+ wait()。
  • resume()スリープ状態をウェイクアップして実行を継続するための待機条件を通知します。

インターフェースと実装を確認しましょう。

// interface class SuspendableWorker : public QObject { Q_OBJECT public: explicit SuspendableWorker(QObject *parent = nullptr); ~SuspendableWorker(); // resume() must be called from the outer thread. void resume(); // suspend() must be called from the outer thread. // the function would block the caller's thread until // the worker thread is suspended. void suspend(); private slots: void suspendImpl(); private: QMutex _waitMutex; QWaitCondition _waitCondition; }; // implementation SuspendableWorker::SuspendableWorker(QObject *parent) : QObject(parent) { _waitMutex.lock(); } SuspendableWorker::~SuspendableWorker() { _waitCondition.wakeAll(); _waitMutex.unlock(); } void SuspendableWorker::resume() { _waitCondition.wakeAll(); } void SuspendableWorker::suspend() { QMetaObject::invokeMethod(this, &SuspendableWorker::suspendImpl); // acquiring mutex to block the calling thread _waitMutex.lock(); _waitMutex.unlock(); } void SuspendableWorker::suspendImpl() { _waitCondition.wait(&_waitMutex); }

中断されたスレッドはquitを受け取らないことに注意してください。イベント。このため、これをバニラで安全に使用することはできませんQThread投稿する前にスレッドを再開しない限り、終了します。これをカスタムに統合しましょうThread防弾にするためのテンプレート。

代替テキスト

template class Thread : QThread { public: explicit Thread(TWorker *worker, QObject *parent = nullptr) : QThread(parent), _worker(worker) { _worker->moveToThread(this); start(); } ~Thread() { resume(); quit(); wait(); } void suspend() { auto worker = qobject_cast(_worker); if (worker != nullptr) { worker->suspend(); } } void resume() { auto worker = qobject_cast(_worker); if (worker != nullptr) { worker->resume(); } } TWorker worker() const { return _worker; } protected: void run() override { QThread::*run*(); delete _worker; } private: TWorker *_worker; };

これらの変更により、quitイベントを投稿する前にスレッドを再開します。また、Thread SuspendableWorkerであるかどうかに関係なく、あらゆる種類のワーカーを渡すことができます。か否か。

使用法は次のようになります。

LogService logService; logService.logEvent('processed event'); logService.suspend(); logService.logEvent('queued event'); logService.resume(); // 'queued event' is now processed.

揮発性vs原子

これはよく誤解されているトピックです。ほとんどの人はvolatileと信じています変数は、複数のスレッドによってアクセスされる特定のフラグを提供するために使用でき、これによりデータの競合状態から保護されます。それは誤りであり、QAtomic*この目的には、クラス(またはstd::atomic)を使用する必要があります。

レスポンシブのために設計する方法

現実的な例を考えてみましょう:TcpConnection専用スレッドで機能する接続クラス。このクラスでスレッドセーフなメソッドをエクスポートする必要があります:bool isConnected()。内部的には、クラスはソケットイベントをリッスンします:connectedおよびdisconnected内部ブールフラグを維持するには:

// pseudo-code, won't compile class TcpConnection : QObject { Q_OBJECT public: // this is not thread-safe! bool isConnected() const { return _connected; } private slots: void handleSocketConnected() { _connected = true; } void handleSocketDisconnected() { _connected = false; } private: bool _connected; }

作成_connectedメンバーvolatile問題を解決するつもりはなく、isConnected()を作るつもりもありませんスレッドセーフ。このソリューションは99%の確率で機能しますが、残りの1%はあなたの人生を悪夢にします。これを修正するには、複数のスレッドから変数アクセスを保護する必要があります。 QReadWriteLockerを使用しましょうこの目的のために:

// pseudo-code, won't compile class TcpConnection : QObject { Q_OBJECT public: bool isConnected() const { QReadLocker locker(&_lock); return _connected; } private slots: void handleSocketConnected() { QWriteLocker locker(&_lock); _connected = true; } void handleSocketDisconnected() { QWriteLocker locker(&_lock); _connected = false; } private: QReadWriteLocker _lock; bool _connected; }

これは確実に機能しますが、「ロックフリー」アトミック操作を使用するほど高速ではありません。 3番目の解決策は高速でスレッドセーフです(例ではstd::atomicの代わりにQAtomicIntを使用していますが、意味的にはこれらは同じです)。

// pseudo-code, won't compile class TcpConnection : QObject { Q_OBJECT public: bool isConnected() const { return _connected; } private slots: void handleSocketConnected() { _connected = true; } void handleSocketDisconnected() { _connected = false; } private: std::atomic _connected; }

結論

この記事では、Qtフレームワークとの並行プログラミングに関するいくつかの重要な懸念事項について説明し、特定のユースケースに対処するためのソリューションを設計しました。アトミックプリミティブの使用、読み取り/書き込みロックなどの単純なトピックの多くは考慮していませんが、これらに興味がある場合は、以下にコメントを残して、そのようなチュートリアルを依頼してください。

Qmakeの探索に興味がある場合は、最近公開しました Qmakeの重要なガイド 。素晴らしい読み物です!

基本を理解する

マルチスレッドはどのように役立ちますか?

このマルチスレッドモデルは、開発者に同時実行のための便利な抽象化を提供します。ただし、単一のプロセスに適用すると、マルチプロセッサシステムでの並列実行が可能になります。

マルチスレッドとは何ですか?

マルチスレッドは、単一のプロセスのコンテキスト内に複数のスレッドが存在できるようにするプログラミングおよび実行モデルです。これらのスレッドはプロセスのリソースを共有しますが、独立して実行できます。

Qtアプリケーションとは何ですか?

Qtフレームワークで構築されたアプリケーション。フレームワーク自体がC ++で構築されているため、QtアプリケーションはC ++で構築されることがよくあります。ただし、Python-Qtなどの他の言語バインディングがあります。

Qtでサポートされている並行モデルは何ですか?

QThreadPoolとQRunnableを活用したタスクベースの並行性、およびQThreadクラスを使用したスレッドプログラミング。

Qt開発者はどの同期プリミティブを利用できますか?

最も頻繁に使用されるのは、QMutex、QSemaphore、およびQReadWriteLockです。 QAtomic *クラスによって提供されるロックフリーのアトミック操作もあります。

業界の目覚め:マットレス業界の混乱

財務プロセス

業界の目覚め:マットレス業界の混乱
著名なデザイン会議2020

著名なデザイン会議2020

Uiデザイン

人気の投稿
共謀:iOSのMultipeerConnectivityを使用した近くのデバイスネットワーキング
共謀:iOSのMultipeerConnectivityを使用した近くのデバイスネットワーキング
コンテンツ戦略ディレクター
コンテンツ戦略ディレクター
生産的な行動の誘発:仕事の動機付けのヒント
生産的な行動の誘発:仕事の動機付けのヒント
ビジネスアナリスト-戦略と分析
ビジネスアナリスト-戦略と分析
異文化デザインとUXの役割
異文化デザインとUXの役割
 
VUIの設計-音声ユーザーインターフェイス
VUIの設計-音声ユーザーインターフェイス
AWSでのTerraformによるダウンタイムゼロのJenkins継続的デプロイ
AWSでのTerraformによるダウンタイムゼロのJenkins継続的デプロイ
アジャイルサーバントリーダーシップを通じて変化を促進する方法
アジャイルサーバントリーダーシップを通じて変化を促進する方法
ファミリーオフィス投資ガイド:ベンチャーキャピタルの代替案
ファミリーオフィス投資ガイド:ベンチャーキャピタルの代替案
WordPressを嫌いではない:5つの一般的なバイアスが暴かれる
WordPressを嫌いではない:5つの一般的なバイアスが暴かれる
人気の投稿
  • c ++の方法
  • 経済に対する負の金利の全体的な影響に関して、エコノミスト
  • AC法人とS法人とは
  • 偽のクレジットカード番号2017
  • C ++でプログラミングする方法を学ぶ
  • レスポンシブデザインのCSSメディアクエリ
カテゴリー
Webフロントエンド リモートの台頭 ツールとチュートリアル 技術 ブランドデザイン エンジニアリング管理 モバイル アジャイル デザイナーライフ 収益性と効率性

© 2021 | 全著作権所有

apeescape2.com