Domanda

Ho posto questo problema su molti forum popolari ma nessuna risposta concreta. La mia applciation utilizza la comunicazione seriale per interfacciarsi con sistemi esterni ognuno con il proprio protocollo di interfaccia. I dati ricevuti dai sistemi vengono visualizzati su una GUI creata in Qt 4.2.1.

La struttura dell'applicazione è tale che

  1. All'inizio dell'app abbiamo una pagina di accesso con una scelta di quattro moduli. Questo è implementato come un maindisplay classe. Ciascuno dei quattro moduli è a classe separata in sé. Il modulo in questione qui è di classe d'azione che è responsabile della raccolta e della visualizzazione di dati da vari sistemi.

  2. L'autenticazione dell'utente lo ottiene nella schermata di azione. Il costruttore della schermata di azione la classe viene eseguita e a parte banale inizializzazione inizia il thread dei singoli sistemi che sono implementato come singleton.

Ogni protocollo di sistema è implementato come thread singleton del modulo:

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
};

Nel file di implementazione * .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();
   } 
}

Struttura della funzione di esecuzione

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();
}

Nella classe della schermata dell'azione ho definito un InputSignalHandler usando sigaction e saio. Questo è un puntatore a funzione che viene attivato non appena arrivano i dati su una delle porte seriali.

È una funzione globale (non possiamo cambiarla in quanto è specifica per Linux) che viene semplicemente utilizzata per confrontare i descrittori di file della porta seriale in cui sono arrivati ??i dati e le fd dei sistemi di sensori, se viene trovata una corrispondenza WaitCondition.wakeOne viene richiamato su quel thread ed esce l'attesa, legge ed elabora i dati.

Nella classe della schermata dell'azione i singoli thread vengono avviati come SensorProtocol :: getInstance () - > start () .

Il protocollo di ciascun sistema ha un frame rate a cui invia i dati. Sulla base di questo fatto, nella schermata delle azioni abbiamo impostato i timer di aggiornamento per il timeout alla frequenza di aggiornamento dei protocolli. Quando questi timer scadono, viene chiamata la funzione UpdateSensorProtocol () della schermata operativa

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

Questo prende un'istanza del sensore singleton come

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
}

Per tutti gli usi dell'istanza singleton viene utilizzato SensorProtocol :: getInstance () . Dato lo scenario di cui sopra, uno dei miei protocolli è sospeso indipendentemente dalle modifiche apportate.

Il blocco si verifica mentre si visualizzano i dati utilizzando UpdateSensorProtocol () Se commento ShowSensorData () nella funzione UpdateSensorProtocol () funziona correttamente. Ma per il resto si blocca e la GUI si blocca. Qualche suggerimento!

Inoltre, poiché il thread principale prende l'istanza corrente di singleton, è davvero multithreading perché stiamo essenzialmente cambiando mUpdate nello stesso singleton anche se dalla schermata di azione.

Sono confuso in questo.

Inoltre, qualcuno può suggerire un design alternativo di quello che sto facendo ora.

Grazie in anticipo

È stato utile?

Soluzione

Prima di tutto, non creare singleton per i sistemi. Usa una sorta di Incapsulamento del contesto per il diverso sistema.

Se ignoe questo consiglio e desideri comunque creare " singletons " i thread utilizzano almeno QApplication :: instance (); come padre del thread e inseriscono QThread :: wait () nei distruttori singleton, altrimenti il ??programma si arresterà in modo anomalo sul uscita dal programma.

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

Ma questo non risolverà il tuo problema ...
Qt è guidato dagli eventi, quindi prova a implementare questa architettura basata sugli eventi molto bella e crea un eventloop per ogni thread di sistema. Quindi puoi creare " Protocolli di sistema " che vivono in altri thread e puoi creare timer, inviare eventi tra thread, ... senza usare oggetti di sincronizzazione di basso livello.
Dai un'occhiata al post di blog di Bradley T. Hughes Percorrere senza mal di testa

Il codice non è stato compilato ma dovrebbe darti una buona idea da dove iniziare ...

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;    
}

Ora hai un totale di sistemi indipendenti, gui e protocolli non si scambiano e non sanno nemmeno che il programma è multithread. Puoi testare tutti i sistemi in modo indipendente in un unico ambiente con thread e incollarli insieme nell'applicazione reale e, se necessario, dividerli tra thread diversi.
Questa è l'architettura del programma che vorrei utilizzare per questo problema. Multithithreading senza un singolo elemento di sincronizzazione di basso livello. Nessuna condizione di gara, nessun blocco, ...

Altri suggerimenti

Problemi:

Usa RAII per bloccare / sbloccare i tuoi mutex. Al momento non sono eccezionalmente sicuri.

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.

Come dovrebbe essere il tuo codice:

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
 }

Usando RAII risolve tutti i problemi che ho individuato sopra.

Su una nota a margine.

Il doppio blocco verificato non funzionerà correttamente.
Usando la variabile di funzione statica suggerita da "Anders Karlsson" risolvi il problema perché g ++ garantisce che le variabili di funzione statica verranno inizializzate solo una volta. Inoltre, questo metodo ha garantito che il singelton sarà correttamente distrutto (tramite distruttore). Al momento, a meno che tu non stia facendo cose fantasiose tramite onexit () perderai memoria.

Vedi qui per molti dettagli su una migliore implementazione di singleton.
C ++ Singleton design pattern

Vedi qui perché il tuo doppio controllo non funziona.
Cosa sono tutti i comuni comportamenti indefiniti che un programmatore C ++ dovrebbe conoscere?

Vorrei iniziare utilizzando RAII (Resource Acquisition Is Initialization) per migliorare la sicurezza del codice di blocco. Hai un codice simile al seguente:

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

Avvolgi il codice mutex all'interno di una classe in cui il mutex viene acquisito nel ctor e rilasciato nel dtor. Ora il tuo codice è simile al seguente:

MyMutex mutex;
...logic...

Il principale miglioramento è che se si generano eccezioni nella parte logica, il tuo mutex viene comunque rilasciato.

Inoltre, non lasciare che eccezioni fuoriescano dai tuoi thread! Catturali anche se non sai come gestirli se non registrandoli da qualche parte.

Non posso essere completamente sicuro di quale sia il problema poiché non ho idea di cosa stia facendo la funzione ShowSensorData () (metodo?), ma ci sono alcuni problemi di multithreading con il codice hanno incluso.

  1. mUpdate dovrebbe essere protetto da un mutex se vi si accede da più di un thread.
  2. Il metodo run () sembra che bloccherà il mutex e non lo rilascerà mai se mStop è vero.

Dovresti prendere in considerazione l'utilizzo delle RAII per afferrare e rilasciare il mutex. Non so se stai usando Qt mutex o no, ma dovresti esaminare QMutexLocker per bloccare e sbloccare i tuoi mutex.

Vorrei prendere in considerazione la modifica della classe SensorProtocol per utilizzare la variabile condizione e un flag o una sorta di evento (non sono sicuro di ciò che Qt ha da offrire qui) per gestire l'aggiornamento all'interno di un metodo associato a l'istanza dell'oggetto. Qualcosa del tipo:

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

Quindi assicurati che il metodo update () afferri il mutex prima di leggere o scrivere uno dei membri che sono condivisi tra il lettore e il display.

Un approccio più completo sarebbe quello di separare la visualizzazione dell'interfaccia utente, i sensori e il loro collegamento utilizzando un architettura Model-View-Controller . Rifattorizzare la soluzione in un'architettura MVC probabilmente semplificherebbe un po 'le cose. Per non parlare del fatto che le applicazioni come questa sono molto meno soggette a errori. Dai un'occhiata ai QAbstractItemView e QAbstractItemDelegate per un'idea su come implementarlo. Da quello che ricordo, c'è un tutorial sull'implementazione di MVC usando Qt da qualche parte ... sono passati parecchi anni da quando ho giocato con Qt.

il tuo metodo getInstance potrebbe essere scritto anche in questo modo per evitare di avere s_instance var:

SensorProtocol& getInstance()
{
  static SensorProtocol instance;
  return instance;
}

Il doppio schema di blocco verificato è interrotto in C ++. Questo è ben documentato su Internet. Non so quale sia il tuo problema ma chiaramente dovrai risolverlo nel tuo codice.

Dai un'occhiata a QextSerialPort :

  

QextSerialPort è multipiattaforma   classe di porta seriale. Questa classe   incapsula una porta seriale su entrambi   POSIX e sistemi Windows.

QextSerialPort eredita da QIODevice e rende le comunicazioni delle porte seriali integrate più agevolmente con il resto dell'API Qt.

Inoltre, è possibile utilizzare uno schema di passaggio dei messaggi per le comunicazioni tra i thread I / O e GUI anziché la memoria condivisa. Questo è spesso molto meno soggetto a errori. Puoi utilizzare la QApplication :: postEvent per inviare messaggi QEvent personalizzati a un QObject da elaborare nel thread della GUI con il gestore QObject :: customeEvent . Si occuperà della sincronizzazione per te e alleggerirà i tuoi problemi di deadlock.

Ecco un esempio veloce e sporco:

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;
};

Inoltre, Qt 4 supporta segnali e slot di accodamento attraverso i thread .

Avere tre thread separati per l'invio, la ricezione e la visualizzazione.

Genera un evento ogni volta che vengono ricevuti dati e gestiscilo all'interno del thread di visualizzazione.

Modifica in risposta al commento 1

Devo ammettere che non so nulla di qt ma da quello che hai detto sembrerebbe ancora che puoi creare il tuo oggetto porta seriale che a sua volta avvia due thread di lavoro (usando un metodo di avvio) per il controllo buffer di input e output.

Se la classe della porta seriale ha un " Connetti alla porta " metodo per ottenere l'uso della porta seriale; un "porto aperto" metodo che avvia i thread di lavoro e apre la porta; a " Chiudi porta " metodo per arrestare i thread di invio e ricezione e una proprietà per l'impostazione di " On Data Received " gestore dell'evento, allora dovresti essere pronto.

La classe non dovrebbe essere un singleton poiché scoprirai che la maggior parte dei sistemi operativi non consentirà a più di un processo di controllare una porta seriale in qualsiasi momento, invece otterrai un'eccezione (che devi handle) quando si tenta di connettersi se è già in uso. I thread di lavoro assicurano che la porta sia mantenuta sotto il tuo controllo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top