I. 概要
スレッドは CPU 動作の基本単位とみなすことができ、プロセスはリソースを操作する基本単位とみなすことができます。プログラムの 1 回の実行をプロセスとみなすことができます。プロセスには多数のスレッドが含まれており、プロセス間でメモリを共有できず、プロセスのメモリはスレッド間で共有されます。プログラムでは、スレッドは独立した同時実行ストリームです。
マルチスレッドは、一般的にマルチタスク分散処理のプロセスで使用されます。タスクのスケジューリング、同時実行に採用される技術で、タスクを実行するスレッドを作成することで、プログラムの実行パフォーマンスを向上させ、CPU 使用効率を向上させ、単一 CPU のマルチスレッド化を実現します。スレッディングはタイムホイールの切り替えであり、マルチ CPU は実際に複数の CPU の作業を同時に実行できます。
2. 特徴
1. プログラムのアドレス空間の共有
2. コードセグメントの共有、データの共有はできない
3. マルチスレッドシングルコアにはスレッドの切り替えがあり、マルチコアは単なるデータ処理ではない
4. マルチスレッドを使用すると、データを迅速に処理し、迅速な実行が可能計算
5. 遅い I/O 操作が終了するのを待っている間、プログラムは他のコンピューティング タスクを実行できます。
6. コンピューティング集約型アプリケーションの場合、マルチプロセッサ システムで実行するために、計算は実装のために複数のスレッドに分解されます。
3. デメリット
マルチスレッドには利点もありますが、欠点もあり、それらは一般に次の側面に現れます。
- デッドロック (2 つのスレッドが互いのリソースの解放を待機している)
- 故障中
- データへの同時アクセスによって引き起こされる問題 (問題のトラブルシューティングが困難)
- 効果がない
4. スレッドの作成方法
1. Qスレッドの継承
スレッド クラスをカスタマイズするには、<QThread> ヘッダー ファイルを追加し、カスタム クラスの run 関数を書き換える必要があります。メイン スレッド コードは run に実装されます。トリガーが完了するまで待機する必要がある場合は、次のことを行う必要があります。 exec() を追加して、イベントをループに入れます。
コード例:
クラス WorkerThread : パブリック QThread
{
Q_OBJECT
void run() オーバーライド {
QString の結果。
/* ... ここにはコストのかかる、またはブロックする操作があります ... */
resultReady(result) を出力します。
}
信号:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread()
{
WorkerThread *workerThread = 新しい WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
ワーカースレッド->start();
}
2. QObjectから継承
QObject カスタム クラスを継承し、スレッドを使用する必要があるクラス ヘッダー ファイルに QThread ヘッダー ファイルを追加し、スレッド メンバー オブジェクトまたはポインターを作成します。その後、呼び出し元でスレッド オブジェクトを作成し、応答信号を接続して、現在のオブジェクトを移動する moveThread 関数 使用するスレッドにオブジェクトを移動し、呼び出しを開始します。このメソッドは Qt によって推奨されています。
サンプルコード:
クラスワーカー : public QObject
{
Q_OBJECT
パブリックスロット:
void doWork(const QString ¶meter) {
QString の結果。
/* ... ここにはコストのかかる、またはブロックする操作があります ... */
resultReady(result) を出力します。
}
信号:
void resultReady(const QString &result);
};
クラスコントローラー : public QObject
{
Q_OBJECT
Qスレッドワーカースレッド;
公共:
コントローラー() {
ワーカー *ワーカー = 新しいワーカー;
ワーカー->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~コントローラー() {
ワーカースレッド.quit();
ワーカースレッド.wait();
}
パブリックスロット:
void handleResults(const QString &);
信号:
void 操作(const QString &);
};
3. QRunnableの継承
QRunnable は、すべての実行可能オブジェクトの基本クラスです。QRunnable を継承し、仮想関数 void QRunnable::run () を書き換えてスレッドの作成を実現することもできますが、一般にこの種のクラスはスレッド プールと組み合わせて使用されます。QThreadPool によって構築されるパラメータは QRunnable, set setAutoDelete ( true ) を実行すると自動削除が完了します。
コード例1 :
#include <QRunnable>
#include <Qスレッド>
#include <Qスレッドプール>
#include <Qデバッグ>
クラス CurrentTask : public QRunnable
{
//スレッド実行タスク: スレッド情報を1 秒ごとに出力します
ボイドラン()
{
for (int nCount = 0; nCount < 5; nCount++)
{
qDebug() << QThread::currentThread();
QThread::msleep(1500);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThreadPool threadpool; //ローカルスレッドプールを構築します
threadpool.setMaxThreadCount(3); //スレッド プール内のスレッドの最大数
for (int nNum = 0; nNum < 150; nNum++)
{
CurrentTask *task = new CurrentTask(this); //スレッド プールで実行できるタスクを構築するループ
タスク->setAutoDelete(true);
threadpool.start(task); //スレッド プールはタスクを実行するためのスレッドを割り当てます
QThread::msleep(1500);
}
a.exec() を返します。
}
例 1 コードは単純なタスクを実行します。通信にシグナルとスロットを使用する必要がある場合は、QObject を継承する必要があります。
例 2:
#include <QRunnable>
#include <Qスレッド>
#include <Qスレッドプール>
#include <Qデバッグ>
クラス CurrentTask: パブリック QObject、パブリック QRunnable
{
Q_OBJECT
//カスタムシグナル
信号:
void 終了();
公共:
void run() {
qDebug() << "こんにちはスレッド : " << QThread::currentThreadId();
終了した()を出力します;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThreadPool threadpool; //ローカルスレッドプールを構築します
threadpool.setMaxThreadCount(3); //スレッド プール内のスレッドの最大数
for (int nNum = 0; nNum < 150; nNum++)
{
CurrentTask *task = new CurrentTask(this); //スレッド プールで実行できるタスクを構築するループ
タスク->setAutoDelete(true);
threadpool.start(task); //スレッド プールはタスクを実行するためのスレッドを割り当てます
QThread::msleep(1500);
}
a.exec() を返します。
}
注: run 関数で tcpsocket の送受信を実装したい場合は、QEventLoopループを追加する必要があります。追加しないとトリガーされず、接続が自動的に切断されます。タスクが完了したら、quit を書くことができます。 ()ループを終了するスロット接続の終了イベント。
4. QtConcurrent::run を使用する
QtConcurrent 名前空間には、同時プログラミングを実現するための多くのメソッドが用意されています。マルチスレッドを実現するには、ヘッダー ファイル #include <QtConcurrent> を追加し、QT += 同時モジュールを pro に追加する必要があります。
サンプルコード:
void func(QString str)
{
qDebug() << __FUNCTION__ << str << QThread::currentThreadId() << QThread::currentThread();
}
void ウィジェット::onBtnWriteClicked()
{
QFuture<void> f1 =QtConcurrent::run(func,QString("aa"));
f1.waitForFinished();
}
5. Qt invokeMethod 非同期
Qt は便利な関数 QMetaObject::invokeMethod を提供します。これは、スレッドの役割と同様に、非同期で呼び出すのに便利です。
関数プロトタイプ:
bool QMetaObject::invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType タイプ、
QGenericReturnArgument ret、
QGenericArgument val0 = QGenericArgument(nullptr),
QGenericArgument val1 = QGenericArgument()、
QGenericArgument val2 = QGenericArgument()、
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument()、
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument()、
QGenericArgument val9 = QGenericArgument())
この関数は、オブジェクトのメンバー (シグナルまたはスロット) を呼び出すために使用されます。メンバーを呼び出すことができる場合は true を返します。そのようなメンバーが存在しない場合、または引数が一致しない場合は false を返します。
QMetaObject::invokeMethod には上記以外に 5 つのオーバーロード関数があるため、ここでは詳しく説明しません。
パラメータの説明:
obj: 呼び出されたオブジェクトへのポインタ
member: メンバーメソッドの名前
タイプ: 接続メソッド、デフォルト値は Qt::AutoConnection です
Qt::DirectConnection の場合、このメンバーはすぐに呼び出されます。(同期呼び出し)
Qt::QueuedConnection では、QEvent が送信され、アプリケーションがメイン イベント ループに入った直後にこのメンバーが呼び出されます。(非同期呼び出し)
Qt::BlockingQueuedConnection の場合、メソッドは Qt::QueuedConnection と同じ方法で呼び出されますが、イベントが配信されるまで現在のスレッドがブロックされる点が異なります。この接続タイプを使用した同じスレッド内のオブジェクト間の通信では、デッドロックが発生します。(非同期呼び出し)
Qt::AutoConnection では、obj が呼び出し元と同じスレッドにある場合はメンバーが同期的に呼び出され、それ以外の場合は非同期的に呼び出されます。
ret: 呼び出された関数の戻り値を受け取る
val0~val9: 呼び出される関数に渡されるパラメータ、最大10個のパラメータ
注: 関数の戻り値をカプセル化するには Q_RETURN_ARG() マクロを使用し、関数パラメーターをカプセル化するには Q_ARG() マクロを使用する必要があります。
サンプルコード:
ブール値の結果;
//同期呼び出し
QMetaObject::invokeMethod(obj, "func", Qt::DirectConnection,
Q_RETURN_ARG(ブール値、結果)、
Q_ARG(QString, "テスト"),
Q_ARG(int, 100);
// 非同期呼び出し
QMetaObject::invokeMethod(obj, "func", Qt::QueuedConnection,
Q_ARG(QString, "テスト"),
Q_ARG(int, 100);
注: Qt::QueuedConnection を使用して非同期的に呼び出すと戻り値を取得できません。この接続メソッドはイベントをイベント キューに渡してすぐに返すことだけを担当するため、関数の戻り値を決定することはできません。 。ただし、上記の Qt::DirectConnection 接続メソッドを使用できます。この接続メソッドは、シグナルを発行するスレッドをブロックし、ブロックを再開する前にキュー接続スロットが戻るまで待機します。これにより、関数の戻り値を確実に取得できます。 。