Qt:多线程编程

来源:互联网 发布:电脑拨号打电话软件 编辑:程序博客网 时间:2024/06/03 17:45
一、创建线程:只需子类化QThread并重新实现它的run()函数就可以了。
//QThread类的start()函数启动run(),可在程序的start()槽中利用QThread子类调用start()。
二、Qt为实现线程的互斥和同步提供了几个常用类:
QMutex,QMutexLocker,QReadWriteLocker,QReadLocker,QWriteLocker,QSemaphore,QWaitCondition
1、mutex.lock();mutex.unlock();mutex.tryLock();
eg:
int createKey(){
mutex.locak();
++key;
return key;
mutex.unlock(); //永远无法执行!!!用QMutexLocker
}
QMutexLocker类在构造函数中接受一个QMutex对象作为参数并将其锁定,在析构函数中解锁这个互斥量。
int createKey(){
QMutexLocker locker(&mutex);
return key;
}//locker作为局部变量在函数退出时结束其作用域,从而自动对互斥量mutex解锁。
/********************************************************************************************************/
2、互斥量只能锁定一次而信号量可以获取多次,它可以用来保护一定数量的同种资源。
semaphore.acquire(n);//用于获取n个资源,但没有足够的资源时调用者将被阻塞直到有足够的可用资源。
semaphore.release(n);semaphore.tryAcquire(n);
生产者消费者解决方案1:
QSemaphore freeBytes(BufferSize);//可被生产者填充的
QSemaphore usedBytes(0);  //可被消费者读取的
class Producer : public QThread{
public:
    void run();
};
void Producer::run(){
    for(int i = 0; i < DataSize; ++i){
        freeBytes.acquire();
        buffer[i%BufferSize] = (i % BufferSize);
        usedBytes.release();
    }
}
class Consumer : public QThread{
public:
    void run();
};
void Consumer::run(){
    for(int i = 0; i < DataSize; ++i){
        usedBytes.acquire();
        fprintf(stderr,"%d",buffer[i % BufferSize]);
        if(i % 16 == 0 && i != 0)
            fprintf(stderr,"\n");
        freeBytes.release();
    }
    fprintf(stderr,"\n");
}
void main(int argc,char *argv[]){
    QCoreApplication app(argc,argv);
    Producer producer;
    Consumer consumer;
    producer.start();
    consumer.start();
    producer.wait();
    consumer.wait();
}
/********************************************************************************************************/
3、使用QWaitCondition:
//对生产者消费者问题的另一个解决方案时使用QWaitCondition,它允许线程在一定条件下唤醒其他线程。
//wakeOne()在条件满足时随机唤醒一个等待线程,而wakeAll()则在条件满足时唤醒所有等待线程。
生产者消费者解决方案2:  //使用了一个生产者,两个消费者线程,以QMutex为等待条件
eg:
const int DataSize = 10000;
const int BufferSize = 8192;
int buffer[BufferSize];
QWaitCondition bufferEmpty;
QWaitCondition bufferFull;
QMutex mutex;
int numUsedBytes = 0;
int rIndex = 0;
void Producer::run(){
    for(int i = 0;i < DataSizze; ++i){
        mutex.lock();//保证原子操作
        if(numUsedBytes == BufferSize)
            bufferEmpty.wait(&mutex);
        buffer[i % BufferSize] = numUsedBytes;
        ++numUsedBytes;
        bufferFull.wakeAll();
        mutex.unlock();
    }
}
void Consumer::run(){
    forever{
        mutex.lock();
        if(numUsedBytes == 0)
            bufferFull.wait(&mutex);
        printf("%ul::[%d]=%d\n",
               currentThreadId(),rIndex,buffer[rIndex]);
        rIndex = (++rIndex)%BufferSize;
        --numUsedBytes;
        bufferEmpty.wakeAll();
        mutex.unlock();
    }
    printf("\n");
}
void main(int argc,char* argv[]){
    QCoreApplication app(argc,argv);
    Producer producer;
    Consumer consumerA;
    Consumer consumerB;
    producer.start();
    consumerA.start();
    consumerB.start();
    producer.wait();
    consumerA.wait();
    consumerB.wait();   
}
/********************************************************************************************************/
4、另一个同步互斥类是QReadWriteLock,它与QMutex十分相似,但具有更高的效率,前者允许并发读取,而后者不允许:
QReadWriteLock允许多个线程同时以只读方式访问,而一旦有写入操作时,所有线程将阻塞知道写入完成,因此,在大量
读取偶尔写入的情况下,QReadWriteLocker非常适合。以上例为例,若以QReadWriteLock进行同步,两个消费者线程可以
实现并发读取。
QReadWriteLock lock;
void ReaderThread::run()
{
    ...
    lock.lockForRead();
    read_file();
    lock.unlock();
    ...
}
void WriterThread::run()
{
    ...
    lock.lockForWrite();
    write_file();
    lock.unlock();
    ...
}
/********************************************************************************************************/
5、优先级问题:
priority()获取当前正在运行线程的优先级,setPriority()设置当前正在运行线程的优先级
void start(Priority priority = InheritPriority) 函数参数设置线程的优先级
enum QThread::Priority
Constant      ValueDescription
QThread::IdlePriority 0scheduled only when no other threads are running.
QThread::LowestPriority 1scheduled less often than LowPriority.
QThread::LowPriority 2scheduled less often than NormalPriority.
QThread::NormalPriority 3the default priority of the operating system.
QThread::HighPriority 4scheduled more often than NormalPriority.
QThread::HighestPriority 5scheduled more often than HighPriority.
QThread::TimeCriticalPriority 6scheduled as often as possible.
QThread::InheritPriority 7use the same priority as the creating thread. This is the default.
//注:Qt所定义的全部线程优先级在Linux下均被映射成了"0"
/********************************************************************************************************/
6、死锁:(4个必要条件:互斥;请求保护;不可剥夺;环路等待)
当带有优先级的线程和同步互斥机制混在一起时,就可能产生另一个问题——“优先级反转”,火星探路者就是一个典例。
/********************************************************************************************************/
7、线程本地存储:
某些应用程序需要在不同的线程中保存全局变量不同的值,这种变量通常被称作线程本地存储(TLS).最典型的应用是全局错误
状态变量,为了保证在一个线程中的修改不会影响其他线程,每个线程需要拥有这个全局变量的一个副本。
Qt提供了QThreadStorage类用于存储线程的单独数据。模板类,限于编译器,只能存储指针。setLocalData()函数为调用线程
存储一份独立的数据,这个数据随后可由localData()函数访问。当所有者线程正常或非正常退出时,所存储的数据(必须用new)
将自动释放。hasLocalData()函数判断线程中是否有经setLocalData()函数存储的数据。
QThreadStorage<QCache<QString,SomeClass> *> caches;
void cacheObject(const QString &key,SomeClass* object){
    if(!caches.hasLocalData())
        caches.setLocalData(new QCache(key,object));
}
void removeFromCache(const QString &key){
    if(!caches.hasLocalData())
        return ;
    caches.localData()->remove(key);
}
//对于每一个调用cacheObject()的线程,QThreadStorage类将为它存储一份独立的数据,而在调用removeFromCache()时将其删除,
//当调用者线程退出时,caches将自动被释放。
/********************************************************************************************************/
8、Qt的线程机制:
一个具有图形用户界面的Qt应用程序只能有一个GUI线程。
编写Qt多线程应用程序的一个棘手问题是,如何在GUI线程和非GUI线程间进行通信。
☀ QObject对象可以用于多线程,其发出的信号能够激活其他线程的槽函数,并且还可以向属于其他线程的对象发送事件。
☀ QObject类是可重入的。大多数非GUI类如QTimer、QTcpSocket、QUdpSocket、QHttp、QFtp和QProcess也是可重入的,从而使得
这些类可以同时用于多个线程。但是GUI类,如QWidget及其子类均是不可重入的,他们只能用于主线程。Qt中线程安全的类包括:
QThread,QMutex,QMutexLocker,QReadWriteLock,QReadLocker,QWriteLocker,QSemaphore,QWaitCondition和QThreadStorage.
此外,函数QApplicaiton::postEvent(),QApplication::removePostedEvent(),QApplication::wakeUp()也是类型安全的。但是
QObject及其子类以及整个事件分发机制都不是线程安全的。
☀ GUI 线程是唯一允许创建QApplication对象并对它调用exec()函数的线程。起始线程通过QCoreApplication::exec()函数启动事件循环



其他非GUI线程通过QThread::exec()启动各自的事件循环。QThread也提供了退出函数exit()和quit()槽。
☀ QObject::thread()获取所属的线程,QObject::moveToThread()函数改变QObject对象所属线程,如果这个对象有父对象无法移动。
如果所启动的线程中没有事件循环,那么事件将不会发往该线程所创建的QObject对象。
☀ 手动调用线程安全的QCoreApplication::postEvent()向任意线程创建的对象发送消息,这个消息最终都会被派发到创建这个对象
所属线程的事件循环。QCoreApplication::sendEvent()函数只能向调用线程中的对象发送消息。
☀ 删除属于其他线程的QObject对象的安全做法是使用QObject::deleteLater()函数,它会发出一个DeferredDelete事件,最终被目标
QObject对象所属线程的事件循环接收处理。



1 0
原创粉丝点击