Как использовать QMutex? - PullRequest
       76

Как использовать QMutex?

16 голосов
/ 23 января 2012

Я новичок в Qt и ищу многопоточность в Qt.
Как я узнал в Документах Qt , я определил два класса для двух потоков:

#include <QThread>
#include <QMutex>

class thread_a : public QThread
{
    Q_OBJECT
public:
    explicit thread_a(QObject *parent = 0);
    int counter;

protected:
    void run();
};

И в файле CPP:

#include "thread_a.h"

thread_a::thread_a(QObject *parent) :
    QThread(parent)
{
    counter=0;
}

void thread_a::run()
{
    counter++;
}

Второй класс потока такой же, но с counter-- в методе run().
Затем я запускаю эти два потока из main.ccp.

Теперь мой вопрос:
Как я могу поделиться counter в thread_a и thread_b, используя QMutex?

Ответы [ 3 ]

28 голосов
/ 23 января 2012

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

Ниже приведен набросок того, что вы могли бы сделать:

class Counter
{
  public:
    Counter():mMutex(),mCounter(0){}
    int inc()
    {
      QMutexLocker ml(&mMutex);
      return mCounter++;
    }
    int dec()
      QMutexLocker ml(&mMutex);
      return mCounter--;
    }
  private:
    QMutex mMutex;
    int mCounter;
    Q_DISABLE_COPY(Counter)
};

class ThreadA : public QThread
{
  public:
    ThreadA(Counter* ctr);
  /* ... */
};

class ThreadB : public QThread
{
  public:
    ThreadB(Counter* ctr);
  /* ... */
};

Конструкция Counter часто упоминается как Монитор из Википедии (выделено мной):

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

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


Правка - Завершить Пример

Использование потоков с общим состоянием правильно не является тривиальным. Вы можете рассмотреть возможность использования сигналов / слотов Qt с подключениями в очереди или другими системами, основанными на сообщениях.

В качестве альтернативы, другие языки программирования, такие как Ada, поддерживают потоки и мониторы (защищенные объекты) в качестве собственных конструкций.

Вот полный рабочий пример. Это только пример кода, не используйте QTest::qSleep в реальном коде.

objs.h

#ifndef OBJS_H
#define OBJS_H

#include <QtCore>

class Counter
{
    public:
        Counter(int init);
        int add(int v);
    private:
        QMutex mMutex;
        int mCounter;
        Q_DISABLE_COPY(Counter)
};

class CtrThread : public QThread
{
    Q_OBJECT
    public:
        CtrThread(Counter& c, int v);
        void stop();
    protected:
        virtual void run();
    private:
        bool keeprunning();
        Counter& mCtr;
        int mValue;
        bool mStop;
        QMutex mMutex;
};

#endif

objs.cpp

#include "objs.h"

Counter::Counter(int i):
    mMutex(),
    mCounter(i)
{}

int Counter::add(int v)
{
    QMutexLocker ml(&mMutex);
    return mCounter += v;
}

///////////////////////////////////////

CtrThread::CtrThread(Counter& c, int v):
    mCtr(c),
    mValue(v),
    mStop(false),
    mMutex()
{}

void CtrThread::stop()
{
    QMutexLocker ml(&mMutex);
    mStop = true;
}

void CtrThread::run()
{
    while(keeprunning())
    {
        mCtr.add(mValue);
    }
}

bool CtrThread::keeprunning()
{
    QMutexLocker ml(&mMutex);
    return ! mStop;
}

test.cpp

#include <QtCore>
#include <QTest>
#include "objs.h"

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    qDebug() << "Initalising";

    Counter ctr(0);
    CtrThread thread_a(ctr, +1);
    CtrThread thread_b(ctr, -1);

    qDebug() << "Starting Threads";

    thread_a.start();
    thread_b.start();

    for (int i = 0; i != 15; ++i)
    {
        qDebug() << "Counter value" << ctr.add(0);
        QTest::qSleep(1000);
    }

    qDebug() << "Stopping Threads";

    thread_a.stop();
    thread_b.stop();
    thread_a.wait();
    thread_b.wait();

    qDebug() << "Finished";
    return 0;
}

test.pro

QT=core testlib
HEADERS=objs.h
SOURCES=test.cpp objs.cpp

Скомпилируйте и запустите, вы увидите напечатанное значение, пример вывода:

Initalising
Starting Threads
Counter value 0
Counter value 11057
Counter value 28697
Counter value 50170
Counter value 60678
Counter value 73773
Counter value 84898
Counter value 96441
Counter value 118795
Counter value 135293
Counter value 146107
Counter value 158688
Counter value 169886
Counter value 201203
Counter value 212983
Stopping Threads
Finished
3 голосов
/ 25 января 2012

ОК, отдельное спасибо @skyhisi за отличное решение для реального проекта.

Я прочитал пост @skyhisi и другие статьи о QMutex и обмене переменными.Для образовательных целей я реализовал простой / понятный пример использования QMutex для обмена переменной (в данном случае counter).

class thread_a : public QThread
{
    Q_OBJECT
public:
    thread_a(QMutex*, int*);
    void shutdown();

private:
    QMutex* mutex;
    int* counter;
    bool isShutdownRequested;

protected:
    void run();
};

и в файле thread_a.cpp:

thread_a::thread_a(QMutex * m, int* i)
{
    counter=i;
    isShutdownRequested=false;
    mutex=m;
}

void thread_a::run()
{
    isShutdownRequested=false;
    forever{
        //lock mutex for changing in shared variable
        mutex->lock();
        *counter=*counter+1;
        mutex->unlock();

        if(isShutdownRequested)
            break;
    }
}

void thread_a::shutdown()
{
    isShutdownRequested=true;
}

ВmyMainWindow::RunThreads(bool bl) слот:

int cnt=0;
QMutex mu;
thread* a=new thread_a(&mu, &cnt);
thread* b=new thread_b(&mu, &cnt);
a.start();
b.start();

В myMainWindow::~myMainWindow() деконструктор:

a->shutdown();
b->shutdown();

Еще раз спасибо @ skyhisi

1 голос
/ 12 февраля 2014

Я пишу простой пример, ссылающийся на «помощь» QMutex, в котором два потока меняют один и тот же номер (как монитор). Это также ссылка на код С.М.Мусави. Вот код:

// main.cpp

#include <QCoreApplication>
#include "method.h"

int aNum=0;
QMutex aMutex;

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int *p_no= &aNum;
    QMutex *p_Mu = &aMutex;

    method mThread1(p_Mu, p_no);
    method mThread2(p_Mu, p_no);

    mThread1.name = "one";
    mThread2.name = "two";

    mThread1.start();
    mThread2.start();

   return a.exec();
}

// method.h

#ifndef METHOD_H
#define METHOD_H

#include <QDebug>
#include <QThread>
#include <QtCore>
#include <QString>
#include <QMutex>


class method: public QThread
{
public:
    method(QMutex *mu, int *nu);
    void run();
    void method1();
    void method2();
    QString name;

private:
    int *number;
    QMutex *myMutex;
};

#endif // METHOD_H

// method.cpp #include "method.h"

method::method(QMutex *mu, int *nu)
{
    myMutex = mu;
    number = nu;
}


void method:: run()
{
    for (int i = 0; i<100; i++)
    {
        if(this->name == "one" )
        {
            this->method1();
        }
        else
        {
            this->method2();
        }
    }
}

void method::method1()
{
    myMutex->lock();
    *number += 1;
    qDebug()<<*number<<"---"<<this->name;
    myMutex->unlock();
}

void method ::method2()
{
    myMutex->lock();
    *number -= 1;
    qDebug()<<*number<<"---"<<this->name;
    myMutex->unlock();
}
...