qt学习笔记6(线程的同步、互斥,线程中的信号与槽,线程与主页面通讯方式,信号和槽的连接方式,线程和对象的生命周期)

一、 线程和进程的区别,线程启动和结束的方式

 

 

 

 (1)线程的启动

               线程的启动方式有上图所示两种,

        第一种“run()”函数,在直接利用对象调用类中的“run()”函数,就相当于效对象中的函数,这时候的run()函数并不是线程中的函数。

        第二种使用“start()”函数,使用这个函数运行对象就是以线程运行的方式,这时候的run()函数就是线程的的形式运行的。

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QDebug>

/*
  sum(n) => 1 + 2 + 3 + ... + n

  sum(1000) => ?

            [1, 1000] =  [1, 300] [301, 600] [601, 1000]
  */

class Calculator : public QThread
{
protected:
    int m_begin;
    int m_end;
    int m_result;
    /*  线程的run()函数 */
    void run()
    {
        qDebug() << objectName() << ": run() begin";

        for(int i=m_begin; i<=m_end; i++)
        {
            m_result += i;

            msleep(10);
        }

        qDebug() << objectName() << ": run() end";
    }
public:
    Calculator(int begin, int end)
    {
        m_begin = begin;
        m_end = end;
        m_result = 0;
    }
    /* 这里直接调用类内函数的方式,并不是启动线程 */
    void work()
    {
        run();
    }

    int result()
    {
        return m_result;
    }
};

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

    qDebug() << "main begin";

    Calculator cal1(1, 300);
    Calculator cal2(301, 600);
    Calculator cal3(601, 1000);

    cal1.setObjectName("cal1");
    cal2.setObjectName("cal2");
    cal3.setObjectName("cal3");

    // cal1.work();  /* 这种方式是无法使用线程的方式运行 */
    // cal2.work();
    // cal3.work();
    /* 使用线程的方式运行run函数 */
    cal1.start();
    cal2.start();
    cal3.start();
    /* 等待线程运行结束 */
    cal1.wait();
    cal2.wait();
    cal3.wait();
    /* 只有等待上面的线程运行结束,才能进行相加的运算 */
    int result = cal1.result() + cal2.result() + cal3.result();

    qDebug() << "result = " << result;

    qDebug() << "main end";
    
    return a.exec();
}

(2)线程的结束停止

         上图所示的“terminate()”函数可以强制停止线程的运行,但是一般都很少使用这种方式,因为这种方式是不管线程程序是否运行结束,线程中使用的资源也会强制释放,这时候是不能得到得到线程中的结果的。

        推荐使用让程序自动结束的方式,也就是让“run()”函数中的“while()”循环结束,可以使用while循环中使用break,也可以使用在while(data)中的‘data’条件,让这个条件不符合即可。

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QDebug>

class Sample : public QThread
{
protected:
    volatile bool m_toStop;

    void run()
    {
        qDebug() << objectName() << " : begin";

        int* p = new int[10000];

        for(int i=0; !m_toStop && (i<10); i++)
        {
            qDebug() << objectName() << " : " << i;

            p[i] = i * i * i;

            msleep(500);
        }

        delete[] p;

        qDebug() << objectName() << " : end";
   }
public:
    Sample()
    {
        m_toStop = false;
    }
    /* 使用变量m_toStop结束for循环,线程的运行也就结束了。 */
    void stop()
    {
        m_toStop = true;
    }
};

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

    qDebug() << "main begin";

    Sample t;

    t.setObjectName("t");

    t.start();

    for(int i=0; i<100000; i++)
    {
        for(int j=0; j<10000; j++)
        {

        }
    }

    t.stop();
    //t.terminate();

    qDebug() << "main end";
    
    return a.exec();
}

 二、线程间的的同步

        这里所讲的线程间的同步也就是使用“wait()”函数实现等待线程的运行结束的处理方案。

 

 

三、线程间的互斥

        线程之间的互斥,主要是为了多个线程同时访问同一个资源的时候,这个资源及时临界资源,同时访问的时候就会产生程序错误。这时候使用线程间的互斥的方式访问临界资源是非常重要的一种方式。

(1)使用互斥锁实现线程间的互斥

 

 

 

 

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>

static QMutex g_mutex;  /* 互斥锁的定义 */
static QString g_store;

class Producer : public QThread
{
protected:
    void run()  /* 线程函数 */
    {
        int count = 0;

        while(true)
        {   /* 互斥锁上锁 */
            g_mutex.lock();
            /* 向全局临界资源中添加数据 */
            g_store.append(QString::number((count++) % 10));

            qDebug() << objectName() << " : " + g_store;
            /* 互斥锁解锁 */
            g_mutex.unlock();

            msleep(1);
        }
    }
};

class Customer : public QThread
{
protected:
    void run()  /* 线程函数 */
    {
        while( true )
        {   /* 上锁 */
            g_mutex.lock();

            if( g_store != "" )
            {   /* 从临界资源中取出资源 */
                g_store.remove(0, 1);

                qDebug() << objectName() << " : " + g_store;
            }
            /* 解锁 */
            g_mutex.unlock();

            msleep(1);
        }
    }
};

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

    Producer p;
    Customer c;
    /* 设置线程的名字,可以使用objectName()函数获得线程的名字 */
    p.setObjectName("Producer");
    c.setObjectName("Customer");
    
    p.start();
    c.start();

    return a.exec();
}

 (2)使用信号量实现线程间的互斥,死锁问题

Ⅰ、死锁问题

  

 

         死锁现象就是两个线程之间使用互斥锁,陷入相互等锁释放的情况。。。。下面就是避免死锁的两种方式:

(1)所有的临界资源都使用同一个锁进行访问,这样的效率就会很低。

(2)程序中锁的分配就是都遵守按照递增的顺序进行获得锁和释放锁,这时候就不会产生死锁的现象了。

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>

QMutex g_mutex_1; /* 锁1 */
QMutex g_mutex_2; /* 锁2 */

class ThreadA : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex_1.lock();  /* 锁1上锁 */

            qDebug() << objectName() << "get m1";

            g_mutex_2.lock();  /* 锁2上锁 */

            qDebug() << objectName() << "get m2";

            qDebug() << objectName() << "do work ...";

            g_mutex_2.unlock();  /* 锁2释放锁 */
            g_mutex_1.unlock();  /* 锁1释放锁 */

            sleep(1);
        }
    }
};

class ThreadB : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex_1.lock();/* 锁1上锁 */

            qDebug() << objectName() << "get m2";

            g_mutex_2.lock();  /* 锁2上锁 */

            qDebug() << objectName() << "get m1";

            qDebug() << objectName() << "do work ...";

            g_mutex_2.unlock();   /* 锁2释放锁 */
            g_mutex_1.unlock();  /* 锁1释放锁 */

            sleep(1);
        }
    }
};

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

    ThreadA ta;
    ThreadB tb;

    ta.setObjectName("ta");
    tb.setObjectName("tb");
    /* 线程的运行 */
    ta.start();
    tb.start();
    
    return a.exec();
}

Ⅱ、线程间的信号量互斥

        信号量的互斥支持多个线程同时访问临界资源的情况。

 

        信号量使用的机制:定义一个信号量,需要有一个初始值,当信号量调用“acquire()”函数的时候,信号量的数值就减一,当信号量调用“release()”函数的时候,信号量的数值就加一。当信号量减一之后小于零的时候就会产生阻塞,也就相当于初始的信号量为零的时候,这时候调用“acquire()”函数获得信号量,信号量减一为“-1”这时候就会产生阻塞而不能运行。

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <Qdebug>

const int SIZE = 5;
unsigned char g_buff[SIZE] = {0};
QSemaphore g_sem_free(SIZE);  /* 信号量1 */
QSemaphore g_sem_used(0);    /* 信号量2 */

class Producer : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            int value = qrand() % 256;

            g_sem_free.acquire();  /* g_sem_free获得信号量,信号量减一 */

            for(int i=0; i<SIZE; i++)
            {   /* 判断数组是否有数据,加了判断之后也就是对数组的这个地址不会同时进行读写了,这也实现了一种阻塞的情况。 */
                if( !g_buff[i] )
                {
                    g_buff[i] = value;

                    qDebug() << objectName() << " generate: {" << i << ", " << value << "}";

                    break;
                }
            }

            g_sem_used.release();  /* g_sem_used释放信号量,信号量加一 */

            sleep(2);
        }
    }
};

class Customer : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_sem_used.acquire();  /* g_sem_used获得信号量,信号量减一 */

            for(int i=0; i<SIZE; i++)
            {
                if( g_buff[i] )
                {
                    int value = g_buff[i];

                    g_buff[i] = 0;

                    qDebug() << objectName() << " consume: {" << i << ", " << value << "}";

                    break;
                }
            }

            g_sem_free.release(); /* g_sem_free释放信号量,信号量加一 */

            sleep(1);
        }
    }
};

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

    Producer p1; /* 实例化线程对象 */
    Producer p2;
    Producer p3;

    p1.setObjectName("p1");
    p2.setObjectName("p2");
    p3.setObjectName("p3");

    Customer c1; /* 实例化线程对象 */
    Customer c2;

    c1.setObjectName("c1");
    c2.setObjectName("c2");

    p1.start();
    p2.start();
    p3.start();

    c1.start();
    c2.start();
    /* 会等待所有的信号全部运行完成 */
    
    return a.exec();
}

        下面是运行的结果:

         上面代码是以工厂为资源的消费者与生产者之间的关系,代码的逻辑如下所示:“Producer ”对象代表生产者,信号量的初始值为5,那么生产者每运行一次就会对信号量减一,对消费者的信号加一,当生产5次之后,在进行生产的时候就会陷入阻塞态;在消费者的线程中对消费者的信号量减一,对生产者的信号量加一,这时候就实现了消费者和生产者之间的阻塞。

        上面的信号里量只是实现了在数组中没有数据的时候进入阻塞态的状况,数组中同一个数据地址之间读写的访问是通过“ if( g_buff[i] )”判断数组中的数据是否有数据在进行读写就实现了读写之间的阻塞。

四、线程与信号槽的联系

(1)线程的一些重要知识

 

 

  注意:“QThread”类中也是可以定义信号函数和槽函数的,并且自身定义的信号函数可以连接自己的槽函数。

#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>

class TestThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit TestThread(QObject *parent = 0);
    
/* 线程中也可以定义信号和槽 */
signals:
    void testSignal();
protected slots:
    void testSlot();
};

(2)槽函数的依赖以及依赖的改变

        槽函数的运行时依赖于槽函数对象的依赖的线程,所以要判断槽函数在那个线程中运行,那么就需要判断这个槽函数属于那个对象,那个对象又是在那个线程中运行的。

        如下图所示的代码,槽函数属于“MyObject”类,这个类实例化的对象依赖于主线程,所以槽函数也是依赖于主线程进行运行的。

 

         对象的依附性和槽函数执行的依附性是可以改变的。改变的方式如下图所示,通过调用对象中的“moveToThread()”函数可以改变对象的依赖性,也就可以改变槽函数的运行所依赖的线程。

        下面所示的代码,对象“t”和对象“m”所依赖的运行线程就是“TestThread ”类中的线程,也就不是主线程了。

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"

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

    qDebug() << "main() tid = " << QThread::currentThreadId();

    TestThread t; /* 依赖于主线程 */
    MyObject m;   /* 依赖于主线程 */
    /* 将m中的槽函数连接到t信号函数,此时槽函数依赖于主线程 */
    /* 槽函数依赖于线程进行运行,对象的依赖的线程是哪个,槽函数就依赖那个线程运行 */
    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));
    /* 改变对象的依赖性 */
    m.moveToThread(&t);
    /* 改变t对象依赖的线程,本来依赖于主线程,现在改变依赖于t自己的线程 */
    t.moveToThread(&t);

    t.start();
    /* 等待8s如果还没有等待事件循环结束,那么就结束等待,进行下面的运行 */
    t.wait(8 * 1000);
    /* 线程事件循环的退出 */
    t.quit();

    return a.exec();
}

注意:将槽函数的运行依赖于线程的时候,需要开启线程的事件循环,不然就不会对槽函数进行处理运行。下图所示的就是开启线程的事件循环处理。使用“QThread”类中提供的“exec()”函数实现开启线程的事件循环。

 

 

 

(3)信号和槽函数的事件循环

        基于“QThread”类创建的对象在依赖于自己的线程运行槽函数的时候,这时候需要运行槽函数,就需要开启线程的事件循环,上面已经介绍了如何开启事件循环。但是开启了事件循环之后,线程运行结束并没有结束事件循环,所以需要使用代码手动关闭事件循环。

 

         下面是线程退出的程序示例:

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"

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

    qDebug() << "main() tid = " << QThread::currentThreadId();

    TestThread t; /* 依赖于主线程 */
    MyObject m;   /* 依赖于主线程 */
    /* 将m中的槽函数连接到t信号函数,此时槽函数依赖于主线程 */
    /* 槽函数依赖于线程进行运行,对象的依赖的线程是哪个,槽函数就依赖那个线程运行 */
    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));
    /* 改变对象的依赖性 */
    m.moveToThread(&t);
    t.moveToThread(&t);

    t.start();
    /* 等待8s如果还没有等待事件循环结束,那么就结束等待,进行下面的运行 */
    t.wait(8 * 1000);
    /* 线程事件循环的退出 */
    t.quit();

    return a.exec();
}
void TestThread::run()
{
    qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId();

    for(int i=0; i<5; i++)
    {
        qDebug() << "void TestThread::run() i = " << i;

        sleep(1);
    }
    /* 发送信号函数 */
    emit testSignal();
    /* 事件循环的开启 */
    exec();

    qDebug() << "void TestThread::run() -- end";
}

五、信号与槽的链接方式

        信号与槽链接方式有下面的几种,在实际使用过程中主要就是线程链接的方式,以及创建的槽函数所属于的运行线程。下面将对这几种连接方式进行逐一的讲解。

(1)立即调用的链接方式(QT::DirectConnection)

        立即调用的链接方式,就相当于将槽函数拷贝到信号发送函数的位置进行运行,槽函数的运行也是依赖发射信号函数位置所依赖的线程进行运行的。

 

         下面是运行的示例代码:

       

#define THREAD_H

#include <QObject>
#include <QThread>
#include <QDebug>
/* 继承QThread类 */
class Thread : QThread
{
    Q_OBJECT
public:
    explicit Thread(QObject *parent = nullptr);

signals:
    void MySignal();

private:
    void run() override;
};

#endif // THREAD_H
#include "Thread.h"

Thread::Thread(QObject *parent) : QThread(parent)
{

}


void Thread::run()
{
    qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId();

    for(int i=0; i<3; i++)
    {
        qDebug() << "void TestThread::run() i = " << i;

        sleep(1);
    }
    /* 信号函数的发送 */
    emit MySignal();
    /* 开启事件循环 */
    exec();

    qDebug() << "void TestThread::run() -- end";
}
#include <QDebug>
#include <QThread>
#include "MyObject.h"
#include "Thread.h"

void direct_connection()
{
    static Thread t;  /* 静态的创建对象 */
    static MyObject m;
    /* 信号函数和槽函数的链接 */
    QObject::connect(&t, SIGNAL(MySignal()), &m, SLOT(ThreadSlot()), Qt::DirectConnection);

    t.start();  /* 线程开始运行 */

    t.wait(5 * 1000);   /* 等待线程运行结束,等待5s之后还没结束就不等了 */
    /* 线程退出事件循环 */
    t.quit();
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //Widget w;
    //w.show();
    qDebug() << "main() tid = " << QThread::currentThreadId();
    direct_connection();
    return a.exec();
}
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>
#include <QDebug>
#include <QThread>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = nullptr);

private slots:
    void ThreadSlot();  /* 槽函数 */
signals:

};

#endif // MYOBJECT_H
#include "MyObject.h"

MyObject::MyObject(QObject *parent) : QObject(parent)
{

}

void MyObject::ThreadSlot()
{   /* QThread::currentThreadId(),打印当前函数运行线程的pid */
    qDebug() << "void MyObject::testSlot() tid = " << QThread::currentThreadId();
}

          下面是运行的结果:

main() tid =  0x7f5edf143780     /* 主线程id */
void TestThread::run() -- begin tid =  0x7f5ec3fff700    /* run()线程的id */
void TestThread::run() i =  0
void TestThread::run() i =  1
void TestThread::run() i =  2
void MyObject::testSlot() tid =  0x7f5ec3fff700     /* 槽函数的id,与上面线程的id是一样的,说明实在线程中运行的槽函数 */
void TestThread::run() -- end

        可以得出结论,立即调用的方式,槽函数的运行依赖于发送信号的函数的线程运行,并且要等待槽函数运行结束才能进行线程中run()函数下一步的运行。

(2)异步调用(Qt::QueuedConnection)

 代码:

void queued_connection()
{
    static Thread t;
    static MyObject m;

    QObject::connect(&t, SIGNAL(MySignal()), &m, SLOT(ThreadSlot()), Qt::QueuedConnection);

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

        运行结果如下所示,从运行结果可以看出槽函数的运行依赖的线程是主主线程。

main() tid =  0x7f399d838780   /*主函数线程 */
void TestThread::run() -- begin tid =  0x7f3982b96700  /* run()线程的id */
void TestThread::run() i =  0
void TestThread::run() i =  1
void TestThread::run() i =  2
void TestThread::run() -- end
void MyObject::testSlot() tid =  0x7f399d838780  /* 槽函数运行的线程id */

       从上面的运行可以得出结论,异步运行是子线程信号发送了之后,就不管槽函数的运行了。子函数的运行依赖于槽函数属于对象的本身依赖的线程,这里槽函数依赖于“MyObject”对象,“MyObject”对象依赖于主线程运行,所以槽函数也是依赖于主线程进行运行的。

(3)同步调用(Qt::BlockingQueuedConnection)

  

        代码:

void blocking_queued_connection()
{
    static Thread t;
    static MyObject m;

    QObject::connect(&t, SIGNAL(MySignal()), &m, SLOT(ThreadSlot()), Qt::BlockingQueuedConnection);

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

        运行结果,下面是结果可以看出槽函数是在主线程中运行的,但是run()函数线程等待槽函数运行结束才结束自己的线程。

main() tid =  0x7f0476d1e780   /* 主线程id */
void TestThread::run() -- begin tid =  0x7f045bfff700   /* run()线程id */
void TestThread::run() i =  0
void TestThread::run() i =  1
void TestThread::run() i =  2
void MyObject::testSlot() tid =  0x7f0476d1e780   /* 槽函数运行id */
void TestThread::run() -- end

        从上面的运行结果可以看出异步调用,槽函数的运行不是在run()函数所在的线程运行的,也是在槽函数所属于的对象所以依赖的线程运行的,但是子线程在“emit MySignal();”代码之后等待槽函数运行结束才结束线程自己。

(4)默认链接(Qt::AutoConnection)

        默认链接也是在使用connect()函数的时候不写连接的形式的时候,所默认的连接方式。有两种连接方式:直接连接异步调用。两种连接使用的情况就是根据发送信号的线程和接收信号的线程是否是同一个线程。

代码:

void auto_connection()
{
    static Thread t;
    static MyObject m;

    QObject::connect(&t, SIGNAL(MySignal()), &m, SLOT(ThreadSlot()));

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

        运行结果,运行结果表明槽函数实在主线程中运行的,运行的方式是异步调用方式。

main() tid =  0x7f5959c21780   /* 主线程id */
void TestThread::run() -- begin tid =  0x7f593ef80700    /* 线程id */
void TestThread::run() i =  0
void TestThread::run() i =  1
void TestThread::run() i =  2
void TestThread::run() -- end
void MyObject::testSlot() tid =  0x7f5959c21780  /*槽函数运行线程id */

        由上面的运行结果可以知道当信号线程和槽函数线程不一样的时候,使用的是异步调用方式。

(5)单一连接(Qt::UniqueConnection)

        单一连接的方式,就是“同一个信号与同一个槽函数之间只有一个连接”。

         普通模式的多次连接,就会调用多次:

void unique_connection()
{
    static Thread t;
    static MyObject m;

    QObject::connect(&t, SIGNAL(MySignal()), &m, SLOT(ThreadSlot()));
    QObject::connect(&t, SIGNAL(MySignal()), &m, SLOT(ThreadSlot()));

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

main() tid =  0x7f1af9c02780
void TestThread::run() -- begin tid =  0x7f1adef80700
void TestThread::run() i =  0
void TestThread::run() i =  1
void TestThread::run() i =  2
void TestThread::run() -- end
void MyObject::testSlot() tid =  0x7f1af9c02780   /* 调用第一次 */
void MyObject::testSlot() tid =  0x7f1af9c02780   /* 调用第二次 */

        单一连接的调用方式,多次connect连接之后,也只会调用一次槽函数。

void unique_connection()
{
    static Thread t;
    static MyObject m;

    QObject::connect(&t, SIGNAL(MySignal()), &m, SLOT(ThreadSlot()), Qt::UniqueConnection);
    QObject::connect(&t, SIGNAL(MySignal()), &m, SLOT(ThreadSlot()), Qt::UniqueConnection);

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

main() tid =  0x7f4078266780
void TestThread::run() -- begin tid =  0x7f4061704700
void TestThread::run() i =  0
void TestThread::run() i =  1
void TestThread::run() i =  2
void TestThread::run() -- end
void MyObject::testSlot() tid =  0x7f4078266780   /* 只调用一次,在主线程中调用 */

六、线程和对象的生命周期

        线程所属对象的生命周期是大于线程的生命周期的。所以线程结束的时候,对象的资源还是没有结束,这时候就需要考虑对象资源释放的问题。

         线程资源的释放分为两种,一种是线程中执行的任务比较少,占有的时间比较少的情况,另一种是线程中执行的任务比较多,线程所占用的时间比较少多的情况,两种资源的释放方式是不同的。

(1)同步线程释放

  

 

        同步型的线程对象资源释放是对象创建在函数中,函数结束,会对对象资源进行释放,也就是线程对象可以被外界主动销毁,释放的时候等待线程运行结束就可以了,下面是代码框架:

(2)异步线程释放

        异步线程释放只支持在堆上进行资源的申请,不能被外界进行线程资源的释放。这时候就需要强制的进行对象资源的释放了。

         异步线程释放的基本思路如下图所示:

        下面是程序设计的框架:

         

 注意:异步型线程的程序设计需要注意,使用“deletelater()”函数,在“run()”函数运行完结束之后调用“deletelater()”函数,就会让系统调用这个对象的析构函数清除对象本身。从而实现异构型程序设计。

七、多线程与页面组件的通讯

        qt中的官方准则对丁,页面的组件只能在主线程中出现,不能在其他页面中出现,所以线程中需要对页面进行更新的时候,需要线程中对主页面的主线程进行发送数据,然后主线程中获得数据之后对页面进行更新。更新页面的方式有两种,一种是通过槽函数进行页面信息的处理,另一种是通过事件的方式进行页面的处理。

(1)线程通过槽函数更新页面

        下面所示的线程中定义页面,更新页面都是错误的。

 

        在子线程中创建有参信号函数,然后在主线程中创建槽函数,当线程中对数据处理好之后,通过信号函数给主线程的槽函数发送数据,然后主线程中的槽函数中接收到数据之后将数据进行处理之后更新到页面组件中。

 

         下面是子线程中的示例代码:

#ifndef UPDATETHREAD_H
#define UPDATETHREAD_H

#include <QThread>

class UpdateThread : public QThread
{
    Q_OBJECT

protected:   
    void run();   /* 线程 */
public:
    explicit UpdateThread(QObject *parent = 0);
    
signals:
    void updateUI(QString text);  /* 信号函数 */
    
};

#endif // UPDATETHREAD_H
#include "UpdateThread.h"

UpdateThread::UpdateThread(QObject *parent) :
    QThread(parent)
{
}

void UpdateThread::run()
{
    emit updateUI("Begin");

    for(int i=0; i<10; i++)
    {
        emit updateUI(QString::number(i));  /* 发送更新文本数据的信号函数 */

        sleep(1);
    }

    emit updateUI("End");
}

        下面是主线程中的数据处理代码:

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include "UpdateThread.h"

class Widget : public QWidget
{
    Q_OBJECT

    UpdateThread m_thread;
    QPlainTextEdit textEdit;
protected slots:
    void appendText(QString text);  /* 主线程中的槽函数,处理线程发过来的数据 */
public:
    Widget(QWidget *parent = 0);
    ~Widget();
};

#endif // WIDGET_H
#include "Widget.h"
#include "TestThread.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    // TestThread* ptt = new TestThread();

    // ptt->start();

    textEdit.setParent(this);
    textEdit.move(20, 20);
    textEdit.resize(200, 150);
    textEdit.setReadOnly(true);

    connect(&m_thread, SIGNAL(updateUI(QString)), this, SLOT(appendText(QString)));

    m_thread.start();
}

void Widget::appendText(QString text)
{
    textEdit.appendPlainText(text);   /* 对页面中的文本数据进行更新 */
}

Widget::~Widget()
{
    
}

(2)线程通过事件函数更新页面

              线程中对主页面发送数据更新页面的方式,可以通过子线程发送事件信号给主线程,主线程中重写对应的事件函数,对发送过来的数据进行处理,处理过之后,对页面进行更新。

 

 

         下面是一个代码案例,主要思想:重定义一个事件类,然后在发送事件类中利用QApplication::postEvent()函数将实例化自己定义的事件进行发送给主线程,然后在主线程的事件函数中接收此事件,并进行处理。

        重定义的事件类:

#ifndef _STRINGEVENT_H_
#define _STRINGEVENT_H_

#include <QEvent>
#include <QString>
/* 重定义事件类,要继承QEvent类才能进行重定义 */
class StringEvent : public QEvent
{
    QString m_data;
public:
    /* 定义本事的Type是什么类型 */
    const static Type TYPE = static_cast<Type>(QEvent::User + 0xFF);
    /* 构造函数 */
    explicit StringEvent(QString data = "");
    /* 获得数据的函数 */
    QString data();
    
};

#endif // _STRINGEVENT_H_
#include "StringEvent.h"

StringEvent::StringEvent(QString data) : QEvent(TYPE)
{
    m_data = data;
}

QString StringEvent::data()
{
    return m_data;
}

        下面是发送事件类线程的代码:

#ifndef UPDATETHREAD_H
#define UPDATETHREAD_H

#include <QThread>

class UpdateThread : public QThread
{
    Q_OBJECT

protected:
    void run(); /* 线程运行函数 */
public:
    explicit UpdateThread(QObject *parent = 0);  
    
};

#endif // UPDATETHREAD_H
#include "UpdateThread.h"
#include <QApplication>
#include "StringEvent.h"

UpdateThread::UpdateThread(QObject *parent) :
    QThread(parent)
{
}

void UpdateThread::run()
{
    // emit updateUI("Begin");
    /* 使用QApplication类发送事件,事件是不需要在类内进行定义的 */
    QApplication::postEvent(parent(), new StringEvent("Begin"));

    for(int i=0; i<10; i++)
    {
        // emit updateUI(QString::number(i));
        QApplication::postEvent(parent(), new StringEvent(QString::number(i)));

        sleep(1);
    }

    // emit updateUI("End");
    QApplication::postEvent(parent(), new StringEvent("End"));
}

        下面是主线程的代码:

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include "UpdateThread.h"

class Widget : public QWidget
{
    Q_OBJECT

    UpdateThread m_thread;
    QPlainTextEdit textEdit;

public:
    Widget(QWidget *parent = 0);
    bool event(QEvent *evt);  /* 事件处理函数 */
    ~Widget();
};

#endif // WIDGET_H
#include "Widget.h"
#include "StringEvent.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{

    textEdit.setParent(this);
    textEdit.move(20, 20);
    textEdit.resize(200, 150);
    textEdit.setReadOnly(true);

    m_thread.setParent(this);
    m_thread.start();
}

bool Widget::event(QEvent *evt)
{
    bool ret = true;
    /* 事件处理函数中判断是什么事件 */
    if( evt->type() == StringEvent::TYPE )
    {   /* 对接收到的事件进行强制转换之后,获得事件对象的指针 */
        StringEvent* se = dynamic_cast<StringEvent*>(evt);

        if( se != NULL )
        {   /* 将事件中的数据添加到页面的文本框中 */
            textEdit.appendPlainText(se->data());
        }
    }
    else
    {
        ret = QWidget::event(evt);
    }

    return ret;
}

Widget::~Widget()
{
    
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值