Подтвердить что ты не робот

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

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

#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?

4b9b3361

Ответ 1

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

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

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 часто называют Monitor, из Википедии (выделено мной):

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

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


Изменить - полный пример

Использование потоков с общим состоянием правильно не является тривиальным. Возможно, вы захотите рассмотреть использование сигналов/слотов 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

Ответ 2

ОК, особая благодарность @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() deconstructor:

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

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

Ответ 3

Я пишу простой пример, ссылающийся на "помощь" 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();
}