C ++中的线程问题

时间:2009-06-23 03:57:00

标签: c++ multithreading qt qt4

我在很多热门论坛上都提到了这个问题但没有具体的回应。我的应用程序使用串行通信与外部系统连接,每个外部系统都有自己的接口协议。从系统接收的数据显示在Qt 4.2.1中的GUI上。

应用程序的结构是这样的

  1. 当应用开始时,我们有一个登录页面 可选择四个模块。这个 被实现为maindisplay 类。四个模块中的每一个都是一个 单独的类本身。这里的相关模块是动作类,负责收集和显示来自各种系统的数据。

  2. 用户身份验证让他/她 进入动作画面。该 动作屏幕的构造函数 类执行并除外 平凡的初始化它开始了 单个系统线程 实现为单身人士。

  3. 每个系统协议都实现为以下形式的单例线程:

    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在该线程上被调用,它出现了wait并读取和处理数据。

    在操作屏幕类中,各个线程以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()的所有用法。鉴于上述情况,无论我做了什么改变,我的一个协议都会挂起。

    使用UpdateSensorProtocol()显示数据时出现挂起如果我在ShowSensorData()中评论UpdateSensorProtocol()函数,它可以正常工作。但否则它会挂起并且GUI冻结。任何建议!

    此外,由于主线程抓取单例的运行实例,它是否真的是多线程的,因为我们实际上是在单例中更改mUpdate,尽管是从操作屏幕。

    我对此感到困惑。

    此外,有人可以提出一个关于我现在正在做什么的替代设计。

    提前致谢

8 个答案:

答案 0 :(得分:5)

首先,不要让系统单身。对某个系统使用某种Context Encapsulation

如果你忽略了这个建议但仍然想创建“单例”线程,至少使用QApplication::instance();作为线程的父级,并将QThread::wait()放在单例析构函数中,否则你的程序将在程序中崩溃出口。

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

但这不会解决你的问题...... Qt是事件驱动的,所以尝试部署这个非常好的事件驱动架构并为每个系统线程创建一个事件循环。然后你可以创建生活在另一个线程中的“SystemProtocols”,你可以创建定时器,在线程之间发送事件,......而不使用低级同步对象。
看看Bradley T. Hughes Treading without the headache

的博客文章

代码没有编译,但应该让你知道从哪里开始......

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

现在你有完全独立的系统,gui和协议现在彼此不相同,甚至不知道程序是多线程的。您可以在单个线程环境中独立地对所有系统进行单元测试,并在实际应用程序中将它们粘合在一起,如果需要,可以在不同的线程之间进行划分。
这是我将用于此问题的程序体系结构。没有单个低级同步元素的多线程。没有竞争条件,没有锁,......

答案 1 :(得分: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++ Singleton design pattern

请参阅此处为何您的双重检查锁定不起作用 What are all the common undefined behaviours that a C++ programmer should know about?

答案 2 :(得分:0)

我首先使用RAII(资源获取初始化)来提高锁定代码的安全性。您的代码如下所示:

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

将互斥锁代码包含在ctor中获取互斥锁并在dtor中释放的类中。现在您的代码如下所示:

MyMutex mutex;
...logic...

主要的改进是,如果逻辑部分有任何异常,你的互斥锁仍会被释放。

另外,不要让任何异常泄漏出你的线程!即使您不知道如何处理它们,除了在某处记录它们之外,请抓住它们。

答案 3 :(得分:0)

我不能完全确定问题是什么,因为我不知道ShowSensorData()函数(方法?)正在做什么,但是你所包含的代码存在一些多线程问题。

  1. mUpdate如果被多个线程访问,则应受互斥锁保护。
  2. run()方法看起来会锁定互斥锁,如果mStop为真,则永远不会释放它。
  3. 您应该考虑使用RAII做法来获取和释放互斥锁。我不知道你是否使用Qt互斥锁,但你应该考虑使用QMutexLocker锁定和解锁你的互斥锁。

    我会考虑更改你的SensorProtocol类以使用条件变量和一个标志或某种事件(不确定Qt在这里提供什么)来处理与对象实例关联的方法内部的更新。类似的东西:

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

    然后确保update()方法在读取或写入读取器和显示器之间共享的任何成员之前抓取互斥锁。

    更完整的方法是使用Model-View-Controller架构分离UI显示,传感器及其链接。将解决方案重构为MVC架构可能会简化相当多的事情。更不用说它使这样的应用程序更容易出错。请查看QAbstractItemViewQAbstractItemDelegate类,了解如何实现此功能。根据我的记忆,有一个关于在某个地方使用Qt实现MVC的教程......自从我使用Qt以来已经有好几年了。

答案 4 :(得分:0)

看看QextSerialPort

  

QextSerialPort是一个跨平台的   串口类。这个班   封装两者上的串口   POSIX和Windows系统。

QextSerialPort继承自QIODevice,使串口通信与Qt API的其余部分更加顺畅地集成。

此外,您可以使用消息传递方案在I / O和GUI线程之间进行通信,而不是共享内存。这通常不易出错。您可以使用QApplication::postEvent函数将自定义QEvent消息发送到要在GUI线程中使用QObject::customeEvent处理程序处理的QObject。它将为您处理同步并缓解您的死锁问题。

这是一个快速而又肮脏的例子:

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 supports queing signals and slots across threads

答案 5 :(得分:0)

你的getInstance方法也可以像这样写,以避免使用s_instance var:

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

答案 6 :(得分:0)

双重检查锁定模式在C ++中被破坏。这在整个互联网上都有很好的记录。我不知道你的问题是什么,但显然你需要在你的代码中解决这个问题。

答案 7 :(得分:0)

有三个单独的线程用于发送,接收和显示。

每当收到数据时提出一个事件并在显示线程中处理它。

编辑以回复评论1

我承认我对qt一无所知但是从你所说的内容看来你仍然可以创建你的串口对象,而后者又启动了两个工作线程(通过使用start方法)输入和输出缓冲控制。

如果串口类具有“连接端口”方法以获得串口的使用;一个“开放端口”方法,用于启动工作线程并打开端口;一个关闭发送和接收线程的“关闭端口”方法和一个用于设置“On Data Received”事件处理程序的属性,那么你应该全部设置。

这个类不应该是一个单例,因为你会发现大多数操作系统在任何时候都不允许多个进程控制一个串口,而是你会得到一个异常(你需要它当你尝试连接时,如果它已经在使用中。工作线程确保端口保持在您的控制之下。