Проблемы с многопоточностью в C ++ - PullRequest
3 голосов
/ 23 июня 2009

Я задавал эту проблему на многих популярных форумах, но не дал конкретного ответа. Мое приложение использует последовательную связь для взаимодействия с внешними системами, каждая из которых имеет свой собственный протокол интерфейса. Данные, полученные от систем, отображаются в графическом интерфейсе, созданном в Qt 4.2.1.

Структура приложения такова, что

  1. Когда приложение начинается, у нас есть страница входа с выбором из четырех модулей. это реализован как maindisplay учебный класс. Каждый из четырех модулей является отдельный класс сам по себе. Данный модуль относится к классу действий, отвечающему за сбор и отображение данных из различных систем.

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

В классе экрана действий я определил InputSignalHandler, используя sigaction и saio. Это указатель на функцию, который активируется, как только данные поступают на любой из последовательных портов.

Это глобальная функция (мы не можем ее изменить, поскольку она специфична для Linux), которая используется только для сравнения файловых дескрипторов последовательного порта, куда поступили данные, и файловых систем сенсорных систем, если найдено совпадение WaitCondition.wakeOne вызывается в этом потоке, и он выходит из режима ожидания, считывает и обрабатывает данные.

В классе экрана действий отдельные потоки запускаются как SensorProtocol::getInstance()->start().

Протокол каждой системы имеет частоту кадров, с которой он отправляет данные. Исходя из этого, на экране действий мы настроили таймеры обновления на тайм-аут с частотой обновления протоколов. Когда время этих таймеров истекает, функция UpdateSensorProtocol () экрана операции вызывается

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

Это захватывает экземпляр датчика singleton как

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(), она работает нормально. Но в противном случае он зависает и графический интерфейс пользователя зависает. Любые предложения!

Кроме того, поскольку основной поток захватывает запущенный экземпляр singleton, действительно ли это многопоточность, потому что мы существенно меняем mUpdate в самом синглтоне, хотя и с экрана действия.

Я запутался в этом.

Кроме того, может кто-нибудь предложить альтернативный дизайн относительно того, что я делаю сейчас.

Заранее спасибо

Ответы [ 8 ]

5 голосов
/ 23 июня 2009

Прежде всего, не делайте системные синглтоны. Использовать какую-то контекстную инкапсуляцию для другой системы.

Если вы игнорируете этот совет и по-прежнему хотите создавать «одиночные» потоки, используйте, по крайней мере, QApplication::instance(); в качестве родителя потока и укажите QThread::wait() в деструкторах-одиночках, иначе ваша программа завершится сбоем при выходе из программы.

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

Но это не решит твою проблему ...
Qt управляется событиями, поэтому попытайтесь использовать эту очень красивую архитектуру, управляемую событиями, и создайте цикл событий для каждого системного потока. Затем вы можете создавать «системные протоколы», которые живут в других потоках, и вы можете создавать таймеры, отправлять события между потоками, ... без использования низкоуровневых объектов синхронизации.
Взгляните на запись в блоге Брэдли Т. Хьюза Поступайте без головной боли

Код не скомпилирован, но должен дать вам хорошее представление, с чего начать ...

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

Теперь у вас полностью независимые системы, графические интерфейсы и протоколы теперь не похожи друг на друга и даже не знают, что программа многопоточная. Вы можете выполнить модульное тестирование всех систем независимо в однопоточном окружении и просто склеить их вместе в реальном приложении и при необходимости разделить их между различными потоками.
Это архитектура программы, которую я бы использовал для решения этой проблемы. Многопоточность без единого элемента синхронизации низкого уровня. Нет условий гонки, нет замков, ...

4 голосов
/ 23 июня 2009

Проблемы:

Используйте 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, он решает все проблемы, которые я заметил выше.

На заметку.

Ваша двойная проверка блокировки не будет работать правильно.
Используя переменную статической функции, предложенную Андерсом Карлссоном, вы решаете проблему, потому что g ++ гарантирует, что переменные статической функции будут инициализированы только один раз. Кроме того, этот метод гарантирует, что сингелтон будет правильно уничтожен (через деструктор). В настоящее время, если вы не выполняете какие-то интересные вещи с помощью onexit (), у вас будет утечка памяти.

См. Здесь много подробностей о лучшей реализации синглтона.
C ++ Шаблон проектирования Singleton

Смотрите здесь, почему ваша двойная проверка блокировки не работает.
Каковы все распространенные неопределенные поведения, о которых должен знать программист C ++?

0 голосов
/ 23 июня 2009

Имеют три отдельные темы для отправки, получения и отображения.

Вызывать событие при получении данных и обрабатывать их в потоке отображения.

Изменить в ответ на комментарий 1

Я признаю, что ничего не знаю о qt, но из того, что вы сказали, все равно будет видно, что вы можете создать свой объект последовательного порта, который, в свою очередь, запускает два рабочих потока (используя метод start) для управление входным и выходным буфером.

Если класс последовательного порта имеет метод «Connect to port» для использования последовательного порта; метод «Открыть порт», который запускает ваши рабочие потоки и открывает порт; метод «Закрыть порт» для отключения потоков отправки и получения, а также свойство для установки обработчика события «При получении данных», после чего все будет готово.

Класс не должен быть одноэлементным, поскольку вы обнаружите, что большинство операционных систем не позволяют нескольким процессам одновременно управлять последовательным портом, вместо этого вы получите исключение (которое вам необходимо обрабатывать), когда вы пытаетесь подключиться, если он уже используется. Рабочие потоки гарантируют, что порт находится под вашим контролем.

0 голосов
/ 23 июня 2009

Шаблон блокировки с двойной проверкой нарушен в C ++. Это хорошо задокументировано во всем Интернете. Я не знаю, в чем ваша проблема, но, очевидно, вам нужно решить эту проблему в своем коде.

0 голосов
/ 23 июня 2009

Ваш метод getInstance может быть также написан так, чтобы избежать появления переменной s_instance:

SensorProtocol& getInstance()
{
  static SensorProtocol instance;
  return instance;
}
0 голосов
/ 23 июня 2009

Посмотрите на QextSerialPort :

QextSerialPort является кроссплатформенным класс последовательного порта. Этот класс инкапсулирует последовательный порт на обоих Системы POSIX и Windows.

QextSerialPort наследуется от QIODevice и обеспечивает более плавную интеграцию связи через последовательный порт с остальной частью Qt API.

Кроме того, вы можете использовать схему передачи сообщений для обмена данными между потоками ввода-вывода и графическим интерфейсом вместо общей памяти. Это часто намного менее подвержено ошибкам. Вы можете использовать функцию QApplication :: postEvent для отправки пользовательских сообщений QEvent в QObject для обработки в потоке GUI с обработчиком QObject :: customeEvent . Он позаботится о синхронизации и облегчит ваши проблемы с тупиком.

Вот быстрый и грязный пример:

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 поддерживает очереди сигналов и слотов между потоками .

0 голосов
/ 23 июня 2009

Я не могу быть полностью уверен, в чем проблема, поскольку я понятия не имею, что делает функция (метод?) ShowSensorData(), но есть некоторые проблемы с многопоточностью в коде, который вы включили.

  1. mUpdate должен быть защищен мьютексом, если к нему обращается более одного потока.
  2. Метод run() выглядит так, как будто он блокирует мьютекс и никогда не освобождает его, если mStop имеет значение true.

Вам следует рассмотреть возможность использования RAII практик для захвата и освобождения мьютекса. Я не знаю, используете ли вы мьютексы Qt или нет, но вы должны рассмотреть возможность использования QMutexLocker для блокировки и разблокировки мьютексов.

Я бы рассмотрел возможность изменения класса SensorProtocol для использования условной переменной и флага или какого-либо события (не уверен, что Qt может предложить здесь) для обработки обновления внутри метода, связанного с экземпляром объекта. Что-то вроде:

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

Затем убедитесь, что метод update() захватывает мьютекс, прежде чем читать или записывать какие-либо элементы, которые совместно используются читателем и дисплеем.

Более полным подходом было бы разделение дисплея пользовательского интерфейса, датчиков и их связи с использованием архитектуры Model-View-Controller . Реорганизация решения в архитектуру MVC, вероятно, немного упростит ситуацию. Не говоря уже о том, что такие приложения намного менее подвержены ошибкам. Взгляните на классы QAbstractItemView и QAbstractItemDelegate , чтобы понять, как это можно реализовать. Из того, что я помню, есть руководство по реализации MVC с использованием Qt где-то ... прошло уже несколько лет, как я играл с Qt.

0 голосов
/ 23 июня 2009

Я бы начал с использования RAII (Resource Acquisition Is Initialization) для повышения безопасности вашего кода блокировки. У вас есть код, который выглядит так:

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

Обертывание кода мьютекса внутри класса, где мьютекс получается в ctor и освобождается в dtor. Теперь ваш код выглядит так:

MyMutex mutex;
...logic...

Основным улучшением является то, что если в логической части возникают исключения, ваш мьютекс все равно освобождается.

Кроме того, не позволяйте исключениям вытекать из ваших тем! Поймай их, даже если ты не знаешь, как справиться с ними, кроме как куда-нибудь войти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...