3

私は多くの人気のあるフォーラムでこの問題を尋ねましたが、具体的な回答はありません。私のアプリケーションは、シリアル通信を使用して、それぞれが独自のインターフェイスプロトコルを持つ外部システムとインターフェイスします。システムから受信したデータは、Qt4.2.1で作成されたGUIに表示されます。

アプリケーションの構造は次のようなものです

  1. アプリが起動すると、4つのモジュールを選択できるログインページが表示されます。これは、maindisplayクラスとして実装されます。4つのモジュールはそれぞれ、それ自体が個別のクラスです。ここで関係するモジュールは、さまざまなシステムからのデータの収集と表示を担当するアクションクラスです。

  2. ユーザー認証により、アクション画面が表示されます。アクション画面クラスのコンストラクターが実行され、ありふれた初期化とは別に、シングルトンとして実装されている個々のシステムスレッドが開始されます。

各システムプロトコルは、次の形式のシングルトンスレッドとして実装されます。

class SensorProtocol:public QThread {    
    static SensorProtocol* s_instance;
    SensorProtocol(){}
    SensorProtocol(const SensorProtocol&);
    operator=(const SensorProtocol&);

public:
    static SensorProtocol* getInstance();
    //miscellaneous system related data to be used for
    // data acquisition and processing
};

実装ファイル*.cpp:

SensorProtocol* SensorProtocol::s_instance=0;
SensorProtocol* SensorProtocol::getInstance()
{
   //DOUBLE CHECKED LOCKING PATTERN I have used singletons
   // without this overrated pattern also but just fyi  
   if(!s_instance)
   {
       mutex.lock();
       if(!s_instance) 
           s_instance=new SensorProtocol();
       mutex.unlock();
   } 
}

実行関数の構造

while(!mStop)
{
  mutex.lock()
  while(!WaitCondition.wait(&mutex,5)
  {
      if(mStop)
      return;    
  }

  //code to read from port when data becomes available
  // and process it and store in variables
  mutex.unlock();
}

アクション画面クラスでは、sigactionとsaioを使用してInputSignalHandlerを定義しました。これは、データがいずれかのシリアルポートに到着するとすぐにアクティブになる関数ポインタです。

これはグローバル関数であり(Linuxに固有であるため変更できません)、一致が見つかった場合に、データが到着したシリアルポートのファイル記述子とセンサーシステムのfdを比較するために使用されます。WaitCondition.wakeOneそのスレッドで呼び出され、待機が終了し、データを読み取って処理します。

アクション画面クラスでは、個々のスレッドはとして開始されSensorProtocol::getInstance()->start()ます。

各システムのプロトコルには、データを送信するフレームレートがあります。この事実に基づいて、アクション画面で、プロトコルのリフレッシュレートでタイムアウトするように更新タイマーを設定しました。これらのタイマーがタイムアウトすると、操作画面のUpdateSensorProtocol()関数が呼び出されます

connect(&timer, SIGNAL(timeout), this,SLOT(UpdateSensorProtocol()));

これにより、センサーシングルトンのインスタンスが次のように取得されます。

SensorProtocol* pSingleton=SensorProtocol::getInstance();
if(pSingleton->mUpdate)
{
    //update data on action screen GUI
   pSingleton->mUpdate=false;  //NOTE : this variable is set to
                               // true in the singleton thread
                               // while one frame is processed completely
}

シングルトンインスタンスのすべての使用にSensorProtocol::getInstance()使用されます。上記のシナリオを考えると、どのような変更を加えても、プロトコルの1つがハングしています。

UpdateSensorProtocol()を使用してデータを表示しているときにハングが発生します。関数にコメントを付けるとShowSensorData()、正常に機能しUpdateSensorProtocol()ます。ただし、それ以外の場合はハングし、GUIがフリーズします。助言がありますか!

また、メインスレッドはシングルトンの実行中のインスタンスを取得するため、アクション画面からではありますが、基本的にシングルトン自体のmUpdateを変更しているため、実際にはマルチスレッドですか。

私はこれで混乱しています。

また、誰かが私が今していることに関して別のデザインを提案できますか?

前もって感謝します

4

8 に答える 8

5

まず、システムのシングルトンを作成しないでください。 異なるシステムには、ある種のコンテキスト カプセル化 を使用します。

このアドバイスを無視して「シングルトン」スレッドを作成したい場合は、少なくともスレッドQApplication::instance();の親として使用しQThread::wait()、シングルトン デストラクタを挿入してください。そうしないと、プログラムの終了時にプログラムがクラッシュします。

if(!s_instance){
    QMutexLocker lock(&mutex);
    if(!s_instance) 
        s_instance=new SensorProtocol( QApplication::instance());
} 

しかし、これでは問題は解決しません...
Qt はイベント駆動型であるため、この非常に優れたイベント駆動型アーキテクチャを活用して、システム スレッドごとにイベントループを作成してみてください。次に、別のスレッドに存在する「SystemProtocols」を作成し、タイマーを作成したり、スレッド間でイベントを送信したりできます...低レベルの同期オブジェクトを使用する必要はありません。
Bradley T. Hughes のブログ エントリをご覧ください

コードはコンパイルされていませんが、どこから始めればよいかがわかります...

class GuiComponent : public QWidget {
    //...
signals: 
    void start(int); // button triggerd signal
    void stop();     // button triggerd singal 

public slots:
    // don't forget to register DataPackage at the metacompiler
    // qRegisterMetaType<DataPackage>();
    void dataFromProtocol( DataPackage ){
        // update the gui the the new data 
    }
};

class ProtocolSystem : public QObject {
     //...
    int timerId;

signals:
    void dataReady(DataPackage);

public slots:
    void stop() {
       killTimer(timerId);  
    }

    void start( int interval ) {
       timerId = startTimer();  
    }

protected:
    void timerEvent(QTimerEvent * event) {
       //code to read from port when data becomes available
       // and process it and store in dataPackage
       emit dataReady(dataPackage);
    }
};

int main( int argc, char ** argv ) {

    QApplication app( argc, argv );
    // construct the system and glue them together
    ProtocolSystem protocolSystem;
    GuiComponent gui;
    gui.connect(&protocolSystem, SIGNAL(dataReady(DataPackage)), SLOT(dataFromProtocol(DataPackage)));
    protocolSystem.connect(&gui, SIGNAL(start(int)), SLOT(start(int)));
    protocolSystem.connect(&gui, SIGNAL(stop()), SLOT(stop()));
    // move communication to its thread
    QThread protocolThread;
    protocolSystem.moveToThread(&protocolThread);
    protocolThread.start(); 
    // repeat this for other systems ...
    // start the application
    gui.show();
    app.exec();
    // stop eventloop to before closing the application
    protocolThread.quit();
    protocolThread.wait();
    return 0;    
}

Now you have total independent systems, gui and protocols don't now each other and don't even know that the program is multithreaded. You can unit test all systems independently in a single threaded environement and just glue them together in the real application and if you need to, divided them between different threads.
That is the program architecture that I would use for this problem. Mutlithreading without a single low level synchronization element. No race conditions, no locks, ...

于 2009-06-23T14:55:01.123 に答える
4

問題:

RAII を使用してミューテックスをロック/ロック解除します。現在、これらは例外セーフではありません。

while(!mStop)
{
  mutex.lock()

  while(!WaitCondition.wait(&mutex,5))
  {
    if(mStop)
    {   
        // PROBLEM 1: You mutex is still locked here.
        // So returning here will leave the mutex locked forever.
        return;    
    }

    // PROBLEM 2: If you leave here via an exception.
    // This will not fire, and again you will the mutex locked forever.
    mutex.unlock();

    // Problem 3: You are using the WaitCondition() incorrectly.
    // You unlock the mutex here. The next thing that happens is a call
    // WaitCondition.wait() where the mutex MUST be locked
 }
 // PROBLEM 4
 // You are using the WaitCondition() incorrectly.
 // On exit the mutex is always locked. So nwo the mutex is locked.

コードは次のようになります。

while(!mStop)
{
  MutextLocker   lock(mutex);  // RAII lock and unlock mutex.

  while(!WaitCondition.wait(&mutex,5))
  {
    if(mStop)
    {   
        return;    
    }

    //code to read from port when data becomes available
    // and process it and store in variables
 }

RAII を使用することで、上記で見つけたすべての問題が解決されます。

余談ですが。

二重チェックのロックは正しく機能しません。
「Anders Karlsson」によって提案された静的関数変数を使用することで、g ++ は静的関数変数が一度だけ初期化されることを保証するため、問題を解決します。さらに、このメソッドは、singelton が (デストラクタを介して) 正しく破棄されることを保証します。現在、onexit() を介して凝った処理を行っていない限り、メモリ リークが発生します。

シングルトンのより良い実装に関する多くの詳細については、こちらを参照してください。
C++ シングルトンの設計パターン

二重チェックのロックが機能しない理由をここで確認してください。
C++ プログラマーが知っておくべき一般的な未定義の動作は何ですか?

于 2009-06-23T05:38:20.140 に答える
0

C++ では、ダブル チェック ロック パターンが壊れています。これは、インターネット全体で十分に文書化されています。あなたの問題が何であるかはわかりませんが、コードでこれを解決する必要があることは明らかです。

于 2009-06-23T04:43:14.400 に答える
0

ロック コードの安全性を向上させるために、まず RAII (Resource Acquisition Is Initialization) を使用します。次のようなコードがあります。

mutex.lock();
...logic...
mutex.unlock();

ミューテックスが ctor で取得され、dtor で解放されるクラス内にミューテックス コードをラップします。コードは次のようになります。

MyMutex mutex;
...logic...

主な改善点は、ロジック部分で例外がスローされた場合でも、ミューテックスが解放されることです。

また、スレッドから例外が漏れないようにしてください。どこかに記録する以外の処理方法がわからない場合でも、それらをキャッチしてください。

于 2009-06-23T04:26:05.337 に答える
0

関数(メソッド?)が何をしているのかわからないので、何が問題なのか完全にはわかりませんがShowSensorData()、あなたが含めたコードにはマルチスレッドの問題がいくつかあります。

  1. mUpdate複数のスレッドからアクセスされる場合は、mutex で保護する必要があります。
  2. このメソッドは、ミューテックスをロックし、 true のrun()場合は決して解放しないように見えます。mStop

RAIIプラクティスを使用してミューテックスを取得および解放することを検討する必要があります。Qtミューテックスを使用しているかどうかはわかりませんが、QMutexLockerを使用してミューテックスをロックおよびロック解除することを検討する必要があります。

クラスを変更SensorProtocolして、オブジェクトインスタンスに関連付けられたメソッド内で更新を処理するために、条件変数とフラグまたは何らかのイベント (Qt がここで提供するものがわからない) を使用することを検討します。何かのようなもの:

/*static*/ void
SensorProtocol::updateSensorProtocol() {
    SensorProtocol *inst = SensorProtocol::getInstance();
    inst->update();
}

次にupdate()、リーダーとディスプレイ間で共有されるメンバーの読み取りまたは書き込みを行う前に、メソッドがミューテックスを取得することを確認します。

より完全なアプローチは、 Model-View-Controllerアーキテクチャを使用して、UI ディスプレイ、センサー、およびそれらのリンクを分離することです。ソリューションを MVC アーキテクチャにリファクタリングすると、おそらくかなり単純になるでしょう。このようなアプリケーションでエラーが発生しにくくなることは言うまでもありません。これを実装する方法については、QAbstractItemViewおよびQAbstractItemDelegateクラスを参照してください。私が覚えていることから、Qtを使用してMVCを実装するためのチュートリアルがどこかにありました... Qtで遊んでからかなりの年月が経ちました。

于 2009-06-23T04:28:38.037 に答える
0

QextSerialPortを見てください:

QextSerialPort は、クロスプラットフォームのシリアル ポート クラスです。このクラスは、POSIX システムと Windows システムの両方でシリアル ポートをカプセル化します。

QextSerialPortはQIODeviceから継承し、シリアル ポート通信を Qt API の残りの部分とよりスムーズに統合します。

また、共有メモリの代わりに、I/O スレッドと GUI スレッド間の通信にメッセージ パッシング スキームを使用することもできます。これにより、多くの場合、エラーが発生しにくくなります。QApplication::postEvent関数を使用して、カスタム QEvent メッセージを QObject に送信し、QObject::customeEventハンドラを使用して GUI スレッドで処理することができます。同期を処理し、デッドロックの問題を軽減します..

これは簡単で汚い例です:

class IODataEvent : public QEvent
{
public:
    IODataEvent() : QEvent(QEvent::User) {}

    // put all of your data here
};

class IOThread : public QThread
{
public:
    IOThread(QObject * parent) : QThread(parent) {}

    void run()
    {
    for (;;) {
            // do blocking I/O and protocol parsing
            IODataEvent *event = new IODataEvent;
            // put all of your data for the GUI into the event
            qApp->postEvent(parent(), event);
            // QApplication will take ownership of the event
        }
    }
};

class GUIObject : public QObject
{
public:
    GUIObject() : QObject(), thread(new IOThread(this)) { thread->start() }

protected:
    void customEvent(QEvent *event)
    {
        if (QEvent::User == event->type) {
            IODataEvent *data = (IODataEvent *) event;
            // get data and update GUI here
            event->accept();
        } else {
            event->ignore();
        }
        // the event loop will release the IODataEvent memory automatically
    }

private:
    IOThread *thread;
};

また、Qt 4はスレッド間のシグナルとスロットのキューイングをサポートしています

于 2009-06-23T04:28:58.927 に答える