精华内容
参与话题
问答
  • Qt线程

    2015-05-31 22:46:01
    Qt中的线程是与平台无关的 QThread 提供了创建一个新线程的方法 新建一个线程,继承QThread并重写它的run()当调用 start()函数时会调用重载的run()函数 例: #ifndef MYTHREAD_H #define MYTHREAD_H ...

    Qt中的线程是与平台无关的

    QThread 提供了创建一个新线程的方法

    新建一个线程,继承QThread并重写它的run()当调用 start()函数时会调用重载的run()函数

    例:

    复制代码
    #ifndef MYTHREAD_H
    #define MYTHREAD_H
    #include <QThread>
    
    class MyThread : public QThread
    {
        Q_OBJECT
    public:
        bool stop ;
        explicit MyThread(QObject *parent = 0);
        void run();
    signals:
    
    public slots:
    
    };
    #endif // MYTHREAD_H
    复制代码
    复制代码
    #include "mythread.h"
    #include<QDebug>
    MyThread::MyThread(QObject *parent) :
        QThread(parent)
    {
        stop = false;
    }
    void MyThread::run()
    {
        for(int i=0;i<1000;i++)
        {
            if(stop)break;
            qDebug()<<i;
            QThread::sleep(1);
        }
    }
    复制代码
    复制代码
    #include <QCoreApplication>
    #include "myobject.h"
    #include <QThread>
    #include<QDebug>
    #include "mythread.h"
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        MyThread myThread;
        myThread.start();
        QThread::sleep(10);
        myThread.stop=true;
        return a.exec();
    }
    复制代码

    看一下Qt中包含的线程类:

    QThread 所有线程的基类,提供了创建一个线程的方法

    QThreadStorge 提供一逐线程数据存储

    QMutex 提供相互排斥的锁,或互斥量

    QMutexLocker 可以自动对QMutex加锁与解锁

    QReadWirterLock 提供了一个可以同时读操作的锁

    QreadLocker与QwriteLocker可以自动对QReadWriteLock加锁与解锁 

    QSempHore提供了一个整形信号量,是互斥的泛化

    QWaitCondition提供了一种方法,使线程可以在被另外线程唤醒之前一直休眠

    线程的同步

    QMutex 提供相互排斥的锁,或互斥量

    QMetex提供了lock和Unlock函数,如果 一个已经锁定 这个互斥量,只有这个线程unlock后其它线程才可以

    访问

    把上边的例子做一下修改,只是为了方便大家理解,这个例子没什么作用

    复制代码
    #ifndef MYTHREAD_H
    #define MYTHREAD_H
    #include <QThread>
    
    class QMutex;
    class MyThread : public QThread
    {
        Q_OBJECT
    private:
        QMutex qm;
        bool stop;
    public:
        explicit MyThread(QObject *parent = 0);
        void run();
        void SetFlg(bool flg);
    signals:
    public slots:
    
    };
    #endif // MYTHREAD_H
    复制代码
    复制代码
    #include "mythread.h"
    #include<QDebug>
    #include<QMutex>
    MyThread::MyThread(QObject *parent) :
        QThread(parent)
    {
        stop = false;
    }
    void MyThread::SetFlg(bool flg)
    {
        qm.lock();
        stop=flg;
        qm.unlock();
    }
    
    void MyThread::run()
    {
        for(int i=0;i<1000;i++)
        {
            qm.lock();
            if(stop)
            {
                qm.unlock();
                break;
            }
            qDebug()<<i;
            QThread::sleep(1);
            qm.unlock();
        }
    }
    复制代码
    复制代码
    #include <QCoreApplication>
    #include "myobject.h"
    #include <QThread>
    #include<QDebug>
    #include "mythread.h"
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        MyThread myThread;
        myThread.start();
        QThread::sleep(10);
        myThread.SetFlg(true);
        return a.exec();
    }
    复制代码

    使用QMutex时要小心因为如果 lock()后没有unlock()会导致死锁别的线程就

    永远也不能访问资源了

    Qt提供了QMutexLocker来解决这个问题

    修改我们的app文件

    复制代码
    #include "mythread.h"
    #include<QDebug>
    #include<QMutex>
    #include<QMutexLocker>
    MyThread::MyThread(QObject *parent) :
        QThread(parent)
    {
        stop = false;
    }
    void MyThread::SetFlg(bool flg)
    {
        QMutexLocker locker(&qm);
        stop=flg;
    }
    
    void MyThread::run()
    {
        QMutexLocker locker(&qm);
        for(int i=0;i<1000;i++)
        {
            if(stop)
            {
                break;
            }
            qDebug()<<i;
            QThread::sleep(1);
    
        }
    }
    复制代码

    QMutexLocker会自己unluck

    QMutexLocker也提供了一个mutex()成员函数返回QMutexLocker操作的互斥量。对于需要访问互斥量是十分有用的,比如QWaitCondition::wait()。

    QReadWirterLock 

    用mutext进行线程同步有一个问题某个时刻只许一个线程访问资源如果同时有多个线程对共享资源进行访问,

    同时有写操作线程那么这种情况下采用mutex就会成为程序的瓶颈。使用QReadWriteLock来实现多线程

    读操作,一个线程写操作,写线程执行时会阻塞所有的读线程,而读线程运行不需要进行同步

    复制代码
    QReadWriteLock lock;
    
    void ReaderThread::run()
    {
        ...
        lock.lockForRead();
        read_file();
        lock.unlock();
        ...
    }
    
    void WriterThread::run()
    {
        ...
        lock.lockForWrite();
        write_file();
        lock.unlock();
        ...
    }
    复制代码

    QreadLocker和QWriteLocker类是对QReadWirterLock 的简化

    它们会自动unluck();

    复制代码
    QReadWriteLock lock;
    
    QByteArray readData()
    {
        QReadLocker locker(&lock);
        ...
        return data;
    }
    复制代码

    相当于QReadWirterLock 的写法如下

    复制代码
    QReadWriteLock lock;
    
    QByteArray readData()
    {
        lock.lockForRead();
        ...
        lock.unlock();
        return data;
    }
    复制代码
    展开全文
  • Qt 线程

    千次阅读 2014-05-29 14:26:55
    参考: Qt线程QThread简析 QThread实例代表一个线程,我们可以重新实现QThread::run(),要新建一个线程,我们应该先继承QThread并重新实现run()函数。 定义一个Thread: class MyThread : public QThread { ...

    Qt 线程

    概述

    参考: Qt线程QThread简析

    QThread实例代表一个线程,我们可以重新实现QThread::run(),要新建一个线程,我们应该先继承QThread并重新实现run()函数。

    定义一个Thread:

    class MyThread : public  QThread
    {
        Q_OBJECT
    protected:
        void run();
    };
    
    void MyThread::run()
    {
        for( int count = 0; count < 20; count++ ){
              sleep( 1 );
        }
    
        qDebug( "finish!");
    }
    

    我们可以在另外的函数这样调用:

    MyThread thread;
    thread.start();
    // 如果没有消息循环,必须要加的语句,等待thread结束。
    thread.wait();
    

    thread.wait()会导致当前线程卡主,使用 QEventLoop 就可以轻松解决此类问题:

    MyThread thread;
    thread.start();
    QEventLoop eventLoop;
    connect(&thread,SIGNAL(finished ()),&eventLoop,SLOT(quit()));
    // thread.wait(1);
    eventLoop.exec();
    

    如果在主线程创建线程,可以直线使用主线程的消息循环:

    QCoreApplication a(argc, argv);
    
    MyThread thread;
    thread.start();
    
    return a.exec();
    

    新建线程的方法

    参考:Qt新建线程的方法

    继承QThread

    继承 QThread,这应该是最常用的方法了。我们可以通过重写虚函数 void QThread::run ()实现我们自己想做的操作,实现新建线程的目的。前面已经介绍了 QThread,这里就不重复了。

    这种方法,我们每一次要新建一个线程都需要继承QThread,实现一个新的类,有点不太方便。但是相对于QRunnable,这种方法的好处就是我们可以直接调用对象的start()函数启动线程,而Qrunnable必须借助 QThreadPool

    继承QRunnable

    QRunnable 是所有可执行对象的基类。我们可以继承 QRunnable,并重写虚函数 void QRunnable::run() 。我们可以用QThreadPool 让我们的一个 QRunnable对象在另外的线程中运行,如果 autoDelete() 返回 true(默认),那么 QThreadPool 将会在 run()运行结束后自动删除 Qrunnable 对象。可以调用 void QRunnable::setAutoDelete ( bool autoDelete ) 更改auto-deletion 标记。需要注意的是,必须在调用 QThreadPool::start() 之前设置,在调用 QThreadPool::start() 之后设置的结果无效。

    下面是我写的简单的例子:

    .h:

    class Runnable:public QRunnable
    {
    
        //Q_OBJECT   注意了,QRunnable不是QObject的子类。
        public:
               Runnable();
               ~Runnable();
               void run();
        protected:
        private:
    };
    

    .cpp:

    // ...
    void Runnable::run()
    {
       cout<<"Runnable::run()thread :"<<QThread::currentThreadId()<<endl;
       cout<<"dosomething ...."<<endl;
    }
    

    main:

    int main(int argc, char *argv[])
    {
           QCoreApplication a(argc, argv);
    
           cout<<"mainthread :"<<QThread::currentThreadId()<<endl;
    
           Runnable runObj;
           QThreadPool::globalInstance()->start(&runObj);
    
           return a.exec();
    }
    

    由结果可看出,run()确实是在不同于主线程的另外线程中运行的,而且在运行结束后就调用了析构函数,因为默认是可以自动被销毁的。
    我们可以对同一个对象多次调用QThreadPool::start(),如果是可以自动被销毁的,Qrunnable对象会在最后一个线程离开了run函数之后才被销毁的。

    Runnable runObj;
    QThreadPool::globalInstance()->start(&runObj);
    QThreadPool::globalInstance()->start(&runObj);
    QThreadPool::globalInstance()->start(&runObj);   
    

    三次调用QThreadPool::globalInstance()->start(&runObj);,但是在三次都执行完之后才运行析构函数。

    这种新建线程的方法的最大的缺点就是:不能使用Qt的信号—槽机制,因为Qrunnable不是继承自QObject。所以我们要想知道线程是否运行结束或获取运行结果可能会比较麻烦。还有就是我们不能直接调用run()启动线程,必须借助于QthreadPool。

    但是这种方法的好处就是,可以让 QThreadPool 来管理线程,QThreadPool 会自动的清理我们新建的Qrunnable对象。

    使用 moveToThread

    首先我们必须实现继承QObject的一个类,实现我们想要的功能。

    class Worker:public QObject
    {
           Q_OBJECT
    public:
        Worker();
        ~Worker();
    protected slots:
        void fun1();
        void fun2();
    };
    
    // ... 构造函数、析构函数实现
    void Worker::fun1()
    {
        cout<<"Worker::fun1()  thread : "<<QThread::currentThreadId()<<endl;
    }
    

    接着创建一个对象,并调用:moveToThread ( QThread * targetThread ),让对象在新的线程中运行。

    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        cout<<"mainthread :"<<QThread::currentThreadId()<<endl;
    
        QThread thread;
        Worker work;
        thread.start();              //注意记得启动线程
    
        work.moveToThread(&thread);
    
        //由于不能直接调用worker的函数,所以一般用信号触发调用
        QTimer::singleShot(0,&work,SLOT(fun1()));  
        QTimer::singleShot(0,&work,SLOT(fun1()));  
        returna.exec();
    }
    

    这样就能让fun1()和fun2()都运行在thread线程中了。

    需要注意的是:在work 的函数结束运行前,thread不能被析构。Thread的生命期不能小于work。否则的话程序就崩掉了。

    像下面的代码肯定是不行的(无消息循环):

    void startWork()
    {
        QThread thread;
        Worker* work = new Worker;
        thread.start();
        work->moveToThread(&thread);
        QTimer::singleShot(0,work,SLOT(fun1()));
        QTimer::singleShot(0,work,SLOT(fun2()));
    }
    

    使用这种方法需要同时管理thread和work,因为都是 new 出来,我们需要负责清理。为了避免这样的麻烦,我想到的方法是,在work类中添加一个QThread成员。

    class Worker:public QObject
    {
           Q_OBJECT
    public:
        Worker();
        ~Worker();
    
    protected slots:
        void fun1();
        void fun2();
    
    private:
        QThread m_thread;
    };
    
    Worker::Worker():QObject()
    {
       m_thread.start();
       this->moveToThread(&m_thread);
    }
    

    这样我们在用的时候只需要 new Work 就行了。

    使用QtConcurrent::run

    其实前面也有用到QtConcurrent::run启动新线程了。QtConcurrent命名空间提供了很多方法可以实现并发编程,这个以后再深入探讨了,这里只是大概讲一下启动线程。还是用上面的worker代码作为例子:

    void Worker::start()
    {
        QtConcurrent::run(this,&Worker::fun1);
        QtConcurrent::run(this,&Worker::fun2);
    }
    

    QtConcurrent::run是个模板函数,有很多种形式,我们也可以让全局的函数允许在另外的线程中。

    void printMes(char*mes)
    {
        cout<<"pprintMes(char*mes)  thread : "<<QThread::currentThreadId()<<endl;
        cout<<mes<<endl;
    }
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        cout<<"mainthread :"<<QThread::currentThreadId()<<endl;
        char *mes= "hello world";
        QtConcurrent::run(printMes,mes);
        returna.exec();
    }
    

    比较

    \ QThread QRunnable QtConcurrent1
    High level API
    Job-oriented
    Builtin support for pause/resume/cancel
    Can run at a different priority
    Can run an event loop

    Threads and QObjects

    QThread 继承 QObject。它可以发送 started 和 finished 信号,也提供了一些 slot 函数。

    QObject 可以用于多线程,可以发送信号调用存在于其他线程的 slot 函数,也可以 postEvent 给其他线程中的对象。之所以可以这样做,是因为每个线程都有自己的事件循环。

    在进行下面的讲解之前,应该了解的重要的一点是: QThread 对象所在的线程,和 QThread 创建的线程,也就是 run() 函数执行的线程不是同一个线程。 QThread 对象所在的线程,就是创建对象的线程。 我们通过一个例子说明更能清楚一点:

    MyThread::MyThread(QObject *parent /* = NULL */ ):QThread(parent)
    {
        qDebug()<< "MyThreadobject currentThreadId :" <<QThread::currentThreadId();
    }
    
    void MyThread::run()
    {
        qDebug()<< "run() currentThreadId : " <<QThread::currentThreadId();
    }
    
    int main( int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        MyThread thread;
        qDebug()<< "mainThread : " <<QThread::currentThreadId();
        thread.start();
    
        return a.exec();
    }
    

    输出结果: MyThread所在的线程就是主线程,run()函数是新开的线程。

    Per-Thread Event Loop

    每个线程都有自己的事件循环。起始的线程用 QCoreApplication::exec () 开启事件循环。其他的线程用 QThread::exec () 开始事件循环。与 QCoreApplication 一样, QThread也提供了 exit (int) 函数 和 quit() 槽函数。

    线程里的事件循环,使得可以在线程里使用需要事件循环的非 GUI 类,例如 ( QTimer , QTcpSocket , and QProcess )。也可以把任意的线程的信号连接到特定线程的槽。

    QObject 实例存在于创建实例的线程中,发送给实例事件也是有线程的事件循环实现的。可以用 QObject::thread () 获取对象存活于哪个线程。

    MyThread::MyThread(QObject*parent /* = NULL */ ):QThread(parent)
    {
        m_pTimer = new QTimer( this );
        qDebug()<< "MyThread object current ThreadId :" <<QThread::currentThread();
    
        QObject obj1;
        obj1.thread();
        qDebug()<< "obj1 live in the thread :" <<obj1.thread();
    
        connect(m_pTimer, SIGNAL(timeout()), this , SLOT(timeOutSlot()));
        //QThread::start();
    }
    
    void MyThread::run()
    {
        QObject obj2;
        obj2.thread();
        qDebug()<< "button2 live in the thread :" <<obj2.thread();
        //m_pTimer->start(500);
        qDebug()<< "run() currentThreadId : " <<QThread::currentThread();
        qDebug( "finish!" );
    }
    

    这个再一次说明 了,对象所处的线程就是创建它的线程。

    注意:对于那些在 QApplication 定义之前创建的对象,QObject::thread () 返回 0 。

    使用 QObject::moveToThread () 函数可以改变对象及其子对象的线程关联度,说白了就是把对象从当前的线程移到另外的线程里。但是如果一个对象已经有了 parent ,那是不能 move 了。

    调用 delete 删除处于另外一个线程的 QObject 对象是不安全的。除非你能保证对象当前不是在进行事件处理。应该用QObject::deleteLater () 替代,这样会发出一个 DeferredDelete 事件,这个事件会最终会被对象所在线程的事件循环所捕获。

    如果没有事件循环,就不会有事件传递给对象。例如,如果你在一个线程中创建了一个QTimer对象,但是不调用exec() , ,那么 QTimer永远不会发出timeout()信号,调用deleteLater() 也不起作用。

    void MyThread::run()
    {
        m_pTimer = new QTimer();
        m_pTimer->start(500);
        connect(m_pTimer, SIGNAL (timeout()), this , SLOT (timeOutSlot()));
    
        qDebug()<< "run() currentThreadId : " <<QThread::currentThread();
        this ->exec();
        //qDebug("finish!" );
    }
    
    void MyThread::timeOutSlot()
    {
        qDebug()<< "timer timeout " ;
        //m_pTimer->stop();
    }
    

    这时候是可以调用 timeOutSlot() 的。

    void MyThread::run()
    {
        m_pTimer = new QTimer();
        m_pTimer->start(500);
        connect(m_pTimer, SIGNAL (timeout()), this , SLOT (timeOutSlot()));
        qDebug()<< "run() currentThreadId : " <<QThread::currentThread();
        //this->exec();
        //qDebug("finish!" );
    }
    

    如果注释 this->exec(); , timeOutSlot() 将不会被调用。

    还有一点要注意的:QTimer对象也不能在另外的线程 stop。如果把 timeOutSlot 里的 m_pTimer->stop(); 取消注释。会看到一行输出:

    QObject::killTimer: timers cannot be stopped from another thread
    

    你可以用QCoreApplication::postEvent () 函数在任意时间给任意线程中的任意对象发送事件,事件自动被创建object的线程的事件循环分发。所有的线程都支持事件过滤器,唯一的限制就是,监视对象必须与被监视对象处于同一个线程。

    同样的,QCoreApplication::sendEvent () 只能用来给与调用 QCoreApplication::sendEvent () 函数处于同一个线程的对象发送事件。说白了就是, QCoreApplication::sendEvent () 不能给处于另外线程的对象发送事件。

    Accessing QObject Subclasses from Other Threads

    QObject 和它所有的子类都不是线程安全的。这包含了整个事件发送系统,需要记住的很重要的一点是:事件循环可能正在给一个对象发送一个事件,同时你可能从别的线程访问该对象。

    如果你调用了一个不是处于当前线程 QObject 子类对象的一个函数,而此时对象可能接收一个事件,你必须用一个 mutex 保护对象的内在的数据。否则,可能引起程序崩溃或者未定义的行为。

    与其他的对象一样, QThread对象存活于创建对象的线程中,而不是存在于QThread::run () 线程。这点在前面讲到了。在自定义QThread子类中提供slot函数是不安全的,除非你用一个 mutex 保护了成员变量。然而,你可以在实现的 QThread::run () 里发出信号,因为信号发送是线程安全的。

    Signals and Slots Across Threads

    Qt 支持了几种信号(槽的连接方式):

    1. Auto Connection ( 默认 ) :如果如果信号的发送方与接收方是处于同一个线程,这个连接就是 Direct Connection ,否则就跟 Queued Connection 一样。
    2. Direct Connection :当信号发出之后,槽会立即被调用。槽函数是在信号发送方的线程中运行的,不需要接收方的线程。
    3. Queued Connection:当控制权回到接收方线程时调用槽函数。槽函数是在接收方的线程中运行的。
    4. Blocking Queued Connection :调用方式跟 Queued Connection 一样,区别在于,当前线程会被阻塞直到槽函数返回。
    5. Unique Connection :这种方式跟 Auto Connection 一样,但是只有当不存在一个相同的连接时才会创建一个连接。如果已经存在相同的连接,则不会创建连接, connect() 返回 false 。

    可以在 connect()添加参数指定连接类型。需要注意的一点是:如果信号发送方和接收方处于不同的线程,而且接收方线程运行着一个事件循环,此时用 Direct Connection 是不安全,原因跟调用一个对象的函数,而这个对象处于另外的线程一样,那样的调用是不安全。

    QObject::connect () 本身是线程安全的。

    下面通过结果例子验证一下:

    class Receiver: public QObject
    {
        Q_OBJECT
        public :
            void sendmes()
            {
                emit emitSignal( "emit message from A To B" );
            }
    
            Receiver()
            {
    
            }
    
            protected  slots :
                void messageSlot(QString mes)
                {
                    qDebug()<<mes;
                }
        signals :
            void emitSignal(QString mes);
    };
    
    int main( int argc, char *argv[])
    {
    
        QApplication a(argc, argv);
        Receiver objA,objB;
        QObject::connect(&objA, SIGNAL (emitSignal(QString)),&objB, SLOT (messageSlot(QString)));
        qDebug()<< "beforeemitsignal " ;
        objA.sendmes();
        qDebug()<< "afteremitsignal " ;
        return a.exec();
    }
    

    objA,objB;出于同一个线程,所以connect的连接类型是Direct Connection

    由输出我们可以看出执行顺序,

    如果我们写了两句连接:

    QObject::connect(&objA, SIGNAL (emitSignal(QString)),&objB, SLOT (messageSlot(QString)));
    QObject::connect(&objA, SIGNAL (emitSignal(QString)),&objB, SLOT (messageSlot(QString)));
    

    就会相应的有两句消息输出:

    如果指定了连接类型 Qt::UniqueConnection ,就会只有一句消息输出了。

    QObject::connect(&objA, SIGNAL (emitSignal(QString)),&objB, SLOT (messageSlot(QString)),Qt::UniqueConnection );
    QObject::connect(&objA, SIGNAL (emitSignal(QString)),&objB, SLOT (messageSlot(QString)),Qt::UniqueConnection);
    

    int main( int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QThread *thread = new QThread;
        thread->start();
        Receiver objA,objB;
        objB.moveToThread(thread);
        QObject::connect(&objA, SIGNAL(emitSignal(QString)), &objB, SLOT(messageSlot(QString)));
        qDebug()<< "beforeemitsignal " ;
        objA.sendmes();
        qDebug()<< "afteremitsignal " ;
        return a.exec();
    }
    

    如果我们把 objB放到另外的线程,connect的连接类型应该是Queued Connection 。

    int main( int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QThread *thread = new QThread;
        thread->start();
        Receiver objA,objB;
        objB.moveToThread(thread);
        QObject::connect(&objA, SIGNAL (emitSignal(QString)),&objB, SLOT (messageSlot(QString)) ,Qt::BlockingQueuedConnection);
        qDebug()<< "beforeemitsignal " ;
        objA.sendmes();
        qDebug()<< "afteremitsignal " ;
        return a.exec();
    }
    

    显示的指定连接类型为 Qt::BlockingQueuedConnection,则输出为:

    可重入和线程安全

    参考:可重入和线程安全简单介绍 - hai200501019的专栏 - 博客频道 - CSDN.NET

    可重入和线程安全这两个术语,经常出现在计算机编程中,用于指明类和函数在多线程程序中的使用。

    可重入 一个类被称为是可重入的:只要在同一时刻至多只有一个线程访问同一个实例,那么我们说多个线程可以安全地使用各自线程内自己的实例。 一个函数被称为是可重入的:如果每一次函数的调用只访问其独有的数据(译者注:全局变量就不是独有的,而是共享的),那么我们说多个线程可以安全地调用这个函数。 也就是说,类和函数的使用者必须通过一些外部的加锁机制来实现访问对象实例或共享数据的序列化。

    若一个函数是可重入的,则该函数:

    1. 不能含有静态(全局)非常量数据。
    2. 不能返回静态(全局)非常量数据的地址。
    3. 只能处理由调用者提供的数据。
    4. 不能依赖于单实例模式资源的锁。
    5. 不能调用(call)不可重入的函数。

    线程安全 如果多个线程可以同时使用一个类的对象,那么这个类被称为是线程安全的;如果多个线程可以同时使用一个函数体里的共享数据,那么这个函数被称为线程安全的。
    (译者注: 更多可重入(reentrant)和t线程安全(thread-safe)的解释: 对于类,如果它的所有成员函数都可以被不同线程同时调用而不相互影响——即使这些调用是针对同一个类对象,那么该类被定义为线程安全。 对于类,如果其不同实例可以在不同线程中被同时使用而不相互影响,那么该类被定义为可重入。在Qt的定义中,在类这个层次,thread-safe是比reentrant更严格的要求)

    线程安全的方法可以被多个线程同时调用,即使这些调用都使用到了共享的数据,因为它对共享数据的使用都是序列化,也就是每个序列操作全部完成之前是不可能被别的线程的使用而中断的。可重入的方法也能被多个线程同时调用,但是必须是每个调用都只使用自己的数据。因此,线程安全的方法必定是可重入的,但是可重入的方法未必是线程安全的。

    一个类是可重入的,指的是,只要每个线程用的是不同的类的实例。一个类是线程安全的,指的是它的成员方法可以安全的被多个线程同时调用,即使每个线程使用的是相同的类实例。

    C++类一般情况下都是可重入的,因为类的成员方法一般都只能操作成员数据,每个线程都是通过自己的类实例调用成员方法,不可能存在别的线程调用同一个类实例的方法。

    以下这个类就不是可重入的,很明显,存在静态数据成员。

    class Counter
    {
    
    public:
        Counter() { }
        static  void decrement() { --n; }
        static  int value() { return n; }
    private:
        static  int n;
    };
    
    
    class Counter
    {
    public:
        Counter() { }
        void increment() { ++n; }
        void decrement() { --n; }
        int value() { return n; }
    private:
        int n;
    };
    

    简单的改一下,把static去掉就可以成为一个可重入的类。但是它并不是线程安全的,当存在多个线程试图修改n的时候,结果就有可能是未定义的。因为++ 和—操作并不是原子性的操作,其包括以下步骤:

    1. 把变量加载到一个寄存器。
    2. 寄存器进行自增或自减。
    3. 把寄存器的值重新保存到变量中。

    假设存在两个线程A和B同时保存了n的旧的的值到寄存器中,再各自进行自增,随后再写回变量,这样便会出现覆盖,n实际也只增加了1。上面说过,就是线程安全方法对数据的操作必须是一序列的不可中断的,就如上面的这个例子,线程A在执行步骤1,2,3必须是连续操作的,在完成3个步骤之前,线程B不允许操作n。因此我们一般情况下必须用互斥变量对数据操作进行加锁。如下

    class Counter
    {
    public:
        Counter() { n = 0; }
        void increment() { QMutexLocker   locker(&mutex); ++n; }
        void decrement() { QMutexLocker    locker(&mutex); --n; }
        int value() const { QMutexLocker   locker(&mutex); return n; }
    private:
        mutable QMutex    int n;
    };
    

    QMutexLocker 对 n 加锁和解锁,保证了对n的操作是序列化的。我们知道const成员方法不能改变实例的任何数据,所以mutex必须的声明为mutable,因为在value() const进行加锁和解锁改变了mutex。

    Qt的许多类都是可重入的,但并不是线程安全的,因为保证线程安全要在每个方法的重复的进行加锁和解锁,那是相当麻烦的。

    可重入与线程安全两个概念都关系到函数处理资源的方式。可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。

    大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供。

    要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分。一般通过加入同步机制以保护共享的资源,使之不会被几个线程同时访问。

    QObject Reentrancy

    QObject 是可重入的,它的大多数非 GUI 子类,例如:

    都是可重入的,使得这些类可以同时用于多线程。需要注意的是,这些类设计为在一个单一的线程中创建和使用的,在一个线程创建对象,而从另外一个线程调用对象的函数并不能保证行得通。有三个限制需要注意:

    1. QObject 的子对象必须在创建其 parent 的线程中创建。这意味着,你不能把 QThread 对象作为 parent 传递给在线程中创建的对象,因为QThread 对象本身在另外一个线程中创建。

    2. 事件驱动对象只能用于单线程。尤其是在定时器机制和网络模块。例如,你不能在不是对象所处的线程中 start 一个计时器或者链接一个 socket 。简单的说就是,你不能在线程 A 创建了一个计时器 timer ,然后在线程 B 从启动 timer 。

    3. 你必须保证在线程中创建的对象要在线程销毁前 delete 。这很容易做到,只要是在 run() 函数栈里创建对象就行。

    我们可以验证一下:

    class MyThread : public QThread
    {
    Q_OBJECT
    
    public :
        MyThread(QObject *parent = NULL);
        ~MyThread();
    public  slots :
        void timeOutSlot();
    protected :
        void run();
        QTimer *m_pTimer;
    };
    
    MyThread::MyThread(QObject*parent /* = NULL */ ):QThread(parent)
    {
        m_pTimer = new QTimer( this );
        qDebug()<< "MyThreadobject currentThreadId :" <<QThread::currentThreadId();
        connect(m_pTimer, SIGNAL (timeout()), this , SLOT (timeOutSlot()));
    }
    
    void MyThread::timeOutSlot()
    {
        qDebug()<< "timer timeout " ;
    }
    
    MyThread::~MyThread()
    {
    
    }
    
    void MyThread::run()
    {
        m_pTimer->start(500);
        qDebug()<< "run() currentThreadId : " <<QThread::currentThreadId();
        qDebug( "finish!" );
    }
    
    int main( int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        MyThread thread;
        qDebug()<< "mainThread : " <<QThread::currentThreadId();
    
        thread.start();
        return a.exec();
    }
    

    Timeout函数并没有被调用,因为 QTimer 在主线程中被创建,而在子线程中启动计时。我们还发现有多了一行输出:

    QObject::startTimer: timers cannot be started from another thread
    

    尽管 QObject 是可重入的,但是 GUI 类,特别是 QWidget 和它的子类都是不可重入的。它们只能在主线程中用。就如前面提到的,QCoreApplication::exec () 必须从主线程进行调用。

    Web Service

    Tutorial

    参考



    转载:http://johnnyfee.github.io/2014/05/03/qt-thread/
    展开全文
  • QT线程

    2015-12-31 21:48:59
    英文原文:http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/ ...一个普遍的问题(也是我厌烦的问题)是如何理解QT线程,还有如何使他们的代码工作。他们将他们的代码,或者基于他们代码的实例展示给我

    英文原文:http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/

    我们之间经常使用IRC(网络聊天工具?)进行交流,社区也同样是这样。我在上 Freenode 网络的 QT 频道时会帮那些有问题的人解答。一个普遍的问题(也是我厌烦的问题)是如何理解QT多线程,还有如何使他们的代码工作。他们将他们的代码,或者基于他们代码的实例展示给我看,而我经常会回复他们一句:

    你做错了。。。

    我知道这个回答是有些无礼的,甚至是有些讽刺的。但我不禁想到,下面的这个类(假想)是一个不正确的多线程原则的应用,也是不正确的QT运用。

    class MyThread : public QThread
    {
    public:
        MyThread()
        {
            moveToThread(this);
        }
        void run();
    signals:
        void progress(int);
        void dataReady(QByteArray);
    public slots:
        void doWork();
        void timeoutHandler();
    };

    我最不能忍受的代码是moveToThread(this); 我看到非常多的人在不理解这句话做了什么的情况下使用它。它做了什么?moveToThread函数让QT确保事件句柄和扩展的信号和槽已经被指定的线程上下文调用,因为QThread是一个线程接口,所以我们让线程的代码运行在“它自己里面”,我们也会在线程运行之前做这样的事。即使这样似乎能工作,但它令人迷惑,而且这也不是QThread设计的使用方式(所有QThread的函数编写时是希望在线程创建时被调用,而不是QThread类初始化时)。 我想,moveToThread(this);如此“恐怖的” (creeps) 写入到人们的代码中是因为他们看到有些博客也这么用。简单的网络搜索就会出现好几篇这样的博客,大家基本都是按照下面的方法做的:

    1. 继承QThread
    2. 添加一些消息和槽来完成工作
    3. 测试代码,发现那些槽并不是从“正确的线程”中调用的
    4. 问Google,找到moveToThread(this);方法,然后加了些注释:“当我添加这个的时候它似乎工作得很好

    在我看来,问题出在第一步。QThread 是被设计来作为接口或者操作系统线程控制点(a control point to an operating system thread)使用的,不是一个放置你的多线程代码的地方。我们面向对象程序员继承一个类是为了扩展或者特殊化(specialize )基类的方法。我想,继承QThread的唯一正当的理由是添加QThread没有的方法,例如,也许要提供一个内存指针来实现线程栈,或者可能是添加实时接口支持。一段下载文件,查询数据库,或者任何其他处理的代码不应该继承QThread。它应该独立(encapsulated)在它自己的对象中。

    通常,这意味着简单的从QThread修改你的代码到QObject,然后,可能需要修改你的类名。当你需要做些初始化时,你可以连接QThread的started信号。你需要实例化一个QThread并使用moveToThread(this);函数指派你的对象到此线程中,让你的代码真正运行在新的线程上下文中。即使你仍然是使用moveToThread函数让QT在指定的线程上下文中运行你的代码,但是我们保持了线程接口的独立性。如果有必要,现在就可能是时候将你的类的多个实例指派到一个单独的线程中,或者多个类的多个实例指派到一个单独的线程中了,换句话说,每个线程绑定一个实例也许是不必要的。(可以减少不必要的线程创建,译者注)

    我常常埋怨那些混淆的线程相关的QT代码的编写。原始的QThread类是抽象的,所以继承是必要的。直到QT4.4,QThread::run()实现了一个默认的定义。在这之前,使用QThread唯一的方法是继承它。但是随着线程相关的添加和不同类别的对象之间的信号和槽的连接,我们突然有了一个方便的线程使用方法。我们喜欢简单,所以我们希望使用它。但不幸的是已经晚了。迫使人们继承QThread已经使它变得比预想的更难以改变。 我也埋怨没有一份最新的示例代码或者文档来告诉人们如何理由便捷的方法使他们从头疼中解脱出来。到现在为止,我发现的最好的资源是一篇我以前写的博客

    免责声明:上面你看到的所有内容是显而易见的观点。我做了很多关于这些类的工作,我很清楚的知道如何来使用或者如何不使用它们。


    使用QThread线程的新方法QObject::moveToThread 
    昨晚在一个邮件列表里面看见一个关于在线程种使用signal/slot的讨论,由于回复太多,这里就不贴出原文了。

            主要是关于怎样从一个线程发送信号到另外一个线程的问题。其实这个也不是什么复杂的问题,在qt的asstant里面已经描诉的比较清楚了。当我们链接信号的时候使用qt::queuedConnection就能使slot在它自己的线程里面运行。

            另我惊讶的是在其中一个的回复种他给出了一些资料,其中一个名为you‘ar doing it wrong。帖子是英文的,由于英文水平有限,加上他所说的使用QT thread的方式和我们平时直接派生QThread实现run函数的方式不一样,所以让我看的非常含糊,甚至到了不清不楚的地步。看了后面的大量的回复 和讨论,勉强明白了它的意思。

        具体请看这里[喝小酒的网摘]http://blog.const.net.cn/a/9900.htm
    http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/

           在那里他提出了一种新的使用QThread的方式,其实也不算是信了,据说qt 4.4就已经有了。那就是QObject::moveToThread。根据QT的asstant的描述,moveToThread的作用是把一个 QOject移动到一个线程里面去,那么它到底是什么意思呢。我的理解就是当我们调用QObject的moveToThread方法之后,我们这个派生自 QObject的类的代码就会在新的线程里面执行。而那篇文章所说的就是大多数对这个函数产生了误解,人们总是在派生的QThread的类的构造函数里面 调用moveToThread(this)以希望把该类的所有函数都在该线程里面执行。这样是错误的。

             今天为了验证这个方法到底有什么用,写了一些代码来做测试。

    1、

       

    1. #include <QObject>  
    2. #include <QDebug>  
    3. #include <QThread>  
    4.   
    5. class MyObject : public QObject {  
    6.     Q_OBJECT  
    7.     public:  
    8.         MyObject() {};  
    9.         ~MyObject() {}  
    10.   
    11.     public slots:  
    12.         void first() {  
    13.             qDebug() << QThread::currentThreadId();  
    14.         }  
    15.         void second() {  
    16.             qDebug() << QThread::currentThreadId();  
    17.         }  
    18.         void three() {  
    19.             qDebug() << QThread::currentThreadId();  
    20.         }  
    21. };  

    2、mainwindow.cxx

    1. #include "mainwindow.h"  
    2. #include <QPushButton>  
    3. #include <QVBoxLayout>  
    4. #include "myobject.h"  
    5.   
    6. MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {  
    7.     my = new MyObject;  
    8.     firstButton = new QPushButton(tr("first"), 0);  
    9.     connect(firstButton, SIGNAL(clicked()), my, SLOT(first()), Qt::QueuedConnection);  
    10.     secondButton = new QPushButton(tr("second"), 0);  
    11.     connect(secondButton, SIGNAL(clicked()), my, SLOT(second()), Qt::QueuedConnection);  
    12.     threeButton = new QPushButton(tr("three"), 0);  
    13.     connect(threeButton, SIGNAL(clicked()), my, SLOT(three()), Qt::QueuedConnection);  
    14.     selfButton = new QPushButton(tr("self"), 0);  
    15.     connect(selfButton, SIGNAL(clicked()), this, SLOT(onSelfPushed()));  
    16.     exitButton = new QPushButton(tr("exit"), 0);  
    17.     connect(exitButton, SIGNAL(clicked()), this, SLOT(onExitPushed()));  
    18.   
    19.     QVBoxLayout *layout = new QVBoxLayout;  
    20.     layout->addWidget(firstButton);  
    21.     layout->addWidget(secondButton);  
    22.     layout->addWidget(threeButton);  
    23.     layout->addWidget(selfButton);  
    24.     layout->addWidget(exitButton);  
    25.   
    26.     QWidget *p = new QWidget;  
    27.     p->setLayout(layout);  
    28.   
    29.     QThread *thread = new QThread;  
    30.     my->moveToThread(thread);  
    31.   
    32.     thread->start();  
    33.     connect(thread, SIGNAL(started()), my, SLOT(first()));  
    34.   
    35.     setCentralWidget(p);  
    36. }  
    37.   
    38. MainWindow::~MainWindow() {  
    39. }  
    40.   
    41. void MainWindow::onFirstPushed() {  
    42.     my->first();  
    43. }  
    44.   
    45. void MainWindow::onSecondPushed() {  
    46.     my->second();  
    47. }  
    48.   
    49. void MainWindow::onThreePushed() {  
    50.     my->three();  
    51. }  
    52.   
    53. void MainWindow::onSelfPushed() {  
    54.     qDebug() << QThread::currentThreadId();  
    55. }  
    56.   
    57. void MainWindow::onExitPushed() {  
    58.     close();  
    59. }  

        通过测试,在mainwidow.cxx使用上面的代码的时候,由于my调用了movetothread函数,那么它所有的槽函数都是执行在新开辟的线程里面。

       如果去掉moveToThread函数,那么所有的函数都将执行在gui线程里面。

       同时为了测试connect的第五个参数,在connect的时候可以将Qt::QueuedConnection修改为Qt::DirectConnection,这样所有的槽函数也将在主线程里面执行。

         最后要注意的是,如果上面connect的时候连接的是this的onXXXXXX槽函数再来调用的my的槽函数的话,那么这些槽函数也将执行在onXXXXX槽函数所在的线程,这里是主线程。

         通过上面的测试,我们在使用线程的时候,就可以将一个类派生自QObject,然后实现所有的signal/slot,然后通过调用 movetothread函数来使他们执行在新的线程里面,而不是每次都要重新派生QThread,并且派生QThread函数的另外一个不好的地方是只 有run函数内部的代码才会执行在新线程里面,相比起来,派生QObject并使用movetothread函数更具有灵活性。

        最后,把讨论中列出的所有的网址列出来哈。

    http://qt-project.org/doc/qt-4.8/thread-basics.html
    http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/
    http://qt-project.org/wiki/Threads_Events_QObjects
    http://qt-project.org/wiki/QThreads_general_usage
    http://labs.qt.nokia.com/2006/12/04/threading-without-the-headache/
    原文:http://blog.csdn.net/sydnash/article/details/7425947[喝小酒的网摘]http://blog.const.net.cn/a/9900.htm
    展开全文
  • QT 线程

    2017-02-10 15:49:19
    线程可以防止ui界面卡死的情况 class WorkerThread : public QThread { Q_OBJECT public: void set_ui(Ui::MainWindow *m); void run() Q_DECL_OVERRIDE; signals: void thread_signal(unsign

    多线程可以防止ui界面卡死的情况


    class WorkerThread : public QThread
    {
        Q_OBJECT
    public:
        void set_ui(Ui::MainWindow *m);
        void run() Q_DECL_OVERRIDE;    
    signals:
        void thread_signal(unsigned int table, unsigned int row);
    
    
    private:
        Ui::MainWindow *my_ui;
    };
    线程的定义 编译时要重新qmake


     ui->runButton->setText("停止");  改变按键上显示的字符
    p->start();    //线程开始
    p->terminate();      //线程退出
    p->isRunning()  //线程是否为运行状态
    connect(p, SIGNAL(thread_signal(unsigned int, unsigned int)), this, SLOT(thread_slot(unsigned int, unsigned int)), Qt::QueuedConnection);
    线程可以自定义信号和其他线程之间连接。这是线程信号与槽的链接方式。thread_slot 可以是其他线程的槽函数。可以由其他线程去执行
    
    
    
    
    线程执行的内容在run函数中,调用start函数时,会自动调用run函数。
    
    

    展开全文
  • qt线程

    2017-07-05 11:26:38
    Qt中的线程是与平台无关的 QThread 提供了创建一个新线程的方法 新建一个线程,继承QThread并重写它的run()当调用 start()函数时会调用重载的run()函数 例: #ifndef MYTHREAD_H #define MYTHREAD_H ...
  • Qt线程间共享数据主要有两种方式: 使用共享内存。即使用一个两个线程都能够共享的变量(如全局变量),这样两个线程都能够访问和修改该变量,从而达到共享数据的目的; 使用singal/slot机制,把数据从一个线程...
  • QT线程及多线程

    2018-05-16 18:59:50
    QT线程及多线程全面解析,案例封装,浅显易懂,运用实例进行验证,多次应用到大型项目中。
  • qt源码地址: ...  Qt的线程应该是大家讨论比较多的,也是迷惑比较多的。这主要和qt线程实现机制和上层封装出的接口有关,封装的不清不楚,接口要么就封装的完全不知道里...
  • QT线程通信

    2013-05-08 09:45:24
    QT Thread 线程加串口通信 供大家学习参考
  • qt 线程与ui线程同步

    千次阅读 2010-07-31 20:51:00
    本文转自:... 然而,当连接位于不同线程中的对象时,这一机制就会变得不同步起来,可以通过修改QObject::connect()的第5个可选参数而改变。 conn
  • Qt自定义事件,Qt线程应用。
  • Qt线程总结

    千次阅读 2020-05-21 19:04:27
    Qt线程提供了支持,它引入了一些基本与平台无关的线程类、线程安全传递事件的方式和全局Qt库互斥 量允许你从不同的线程调用Qt的方法。Qt中与线程应用相关的类如下: 使用线程需要Qt提供相应的线程库的支持,因此...
  • Qt线程详解

    千次阅读 2017-06-30 20:03:00
    线程与并行处理任务息息相关,就像进程一样,线程一般运行在进程里面,一个进程可以有多个线程线程与进程有什么区别呢?当你在打游戏的时候,在相同的桌面上可能有一个播放器正在播放你最喜欢的歌曲。这是一个两...
  • QT线程详解

    2017-09-30 21:44:21
    在讲Qt线程前,先说说什么是线程? 所谓线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中...
  • Qt线程之间通信、线程和进程之间通信实例,简单易懂
  • Qt线程基础

    千次阅读 2011-12-10 13:50:21
    原文地址:http://doc.qt.nokia.com/master-snapshot/thread-basics.html 线程基础 何谓线程? 线程与并行处理任务息息相关,就像进程一样。那么,线程与进程有什么区别呢?当你在电子表格上进行数据结算的...
  • Qt随笔之Qt线程同步

    2013-11-02 20:42:43
    一、Qt线程 1、Qt中的QThread类提供了平台无关的线程。相对于一般的程序都是从main()函数开始执行,QThread从run()函数开始执行。默认的,run()通过调用exec()来开启事件循环。 实现一个简单的继承自QThread的...
  • qt线程睡眠

    2019-09-24 09:31:23
    头文件 #include <QThread> 接口函数: void QThread::sleep ( unsigned long secs ) [static protected] void QThread::msleep ( unsigned long msecs ) [static protected] void QThread::usleep ( unsigne....
  • Qt线程切换

    2019-12-28 16:27:54
    如何在子线程中更新ui,一般方式通过信号槽的Qt::QueuedConnection(或Qt::AutoConnection)方式连接一个槽函数再传递参数到主线程调用,在这里封装这个操作让线程切换更加容易 我们要实现的目标是这样的: void ...
  • QT线程2

    2015-12-31 21:50:29
    介绍 You’re doing it wrong. — Bradley T.... 线程qt channel里最流行的讨论话题之一。许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到的...Qt中创建、运行线程的“易用”性、缺乏相关编程尤其是异

空空如也

1 2 3 4 5 ... 20
收藏数 7,187
精华内容 2,874
关键字:

qt线程