在Qt中使用线程比较简单,只需要继承QThread类并重新实现其run()函数,代码如下

class MyThread : public QThread
{
  Q_OBJECT
projected:
  void run();    
};

void MyThread :: run()
{
 .......
}

只需在run()函数中填写所需要的功能代码,然后创建一个MyThread实例,并以QThread::start()函数启动这个实例就可以了。这样run()函数中的功能代码就运行在一个独立的线程中。

 

多线程程序的特点:

1. 多线程程序的行为无法预测,当我们多次执行同一个程序时,每次运行的结果都不相同;

2. 线程的执行顺序无法保证,它与操作系统的调度策略和线程优先级等因素有关;

3. 线程的切换可能发生在任何时刻任何地点;

4. 线程对代码的敏感高,代码的细微修改可能产生意向不到的结果;

为了有效的使用线程,必须对其进行控制。

 

线程间的关系:互斥关系和同步关系

QT提供的几个类: QMutex、 QmutexLocker、QReadWriteLocker、QReadLocker、QWriteLocker、QSemaphore、QWaitConditon

 

QMutex类提供一个保护一段临界区代码的方法,它每次只允许一个线程访问这段临界区代码

QMutex类的lock函数用来锁住互斥量,如果互斥量处于解锁状态,当前线程就会立即抓住并锁定它;否则当前线程就会被阻塞,直到持有这个互斥量的线程对它解锁。线程调用lock函数后就会一直持有这个互斥量知道再次调用unlock函数。  也就是说 lock和unlock函数是成对出现的。这就出现一个问题,如果说你的功能代码里需要返回一个数据比如:

{

mutex.lock();

............

............


return value;

mutex.unlock();

}

上面的代码显然会有一个问题:return 后的代码是不是执行的,这就会出现这个线程不会释放乇鹗互斥量导致其他行程无法获得互斥量而一直处于阻塞状态;

Qt给出了解决方法,QMutexLocker类可以简化互斥量的处理

The QMutexLocker class is a convenience class that simplifies locking and unlocking mutexes.

Locking and unlocking a QMutex in complex functions and statements or in exception handling code is error-prone and difficult to debug. QMutexLocker can be used in such situations to ensure that the state of the mutex is always well-defined.

QMutexLocker should be created within a function where a QMutex needs to be locked. The mutex is locked when QMutexLocker is created. You can unlock and relock the mutex with unlock() and relock(). If locked, the mutex will be unlocked when the QMutexLocker is destroyed.

构造函数是: QMutexLocker ( QMutex * mutex ),在构造函数中接受一个QMutex对象作为参数并将其锁定,在析构函数中解锁这个互斥量。

那么上面有点问题的代码就可以改成如下:

{

QMutexLocker locker(&mutex);

............

............


return value;

}

上面的locker作为局部变量会在数据退出的时候结束其作用域,从而自动调用析构函数来对互斥量mutex解锁。

 

QSemaphore

信号量对互斥量做了扩充,互斥量只能锁定一次而信号量可以获取多次。acquire()函数用于获取n个资源,当没有足够的资源时调用者会被阻塞,直到有足够的可用资源,release()函数用于释放n个资源。

 

生产者/消费者的问题中有两处关键:如果生产者过快地生产数据,将会覆盖消费者还没有读取的数据;如果消费者过快地读取数据,将越过生产者并且读取到一些过期数据。

两个关键的两个处理方法:

1.先让生产者填满整个缓冲区,然后等消费者读取整个消费区;

2.让生产者和消费者同时分别操作缓冲区的不同部分,需要两个信号量;

对于方法2,比如:

QSemaphore freeBytes(BufferSize);

QSemaphore usedBytes(0);

freeBytes信号量控制可被生产者填充的缓冲区部分,usedBytes信号量控制可被消费者读取的缓冲区部分。这两个信号量互为补充的。其中freeBytes信号量被初始化为最大,usedBytes被初始化为0

例程见QT自带例程的examples\threads\semaphores

 

除了互斥与同步控制,多线程编程还涉及死锁和优先级控制两个经典话题。

Linux中的线程调度策略:

SCHED_FIFO  先进先出

SCHED_RR   轮转

SCHED_OTHER  其他

死锁和优先级反转,两者在很大程度上都是因为线程的互斥与同步操作不当引起的。

死锁的产生必须具备以下4个条件:

1.互斥条件

2.请求保护条件

3.不可剥夺条件

4.环路等待条件

 

在具有图形用户界面的Qt应用程序中,主线程由GUI线程充当,该线程是Qt中唯一可以执行GUI相关操作的线程,即一个具有图形用户界面的Qt应用程序只能有一个GUI线程。但却可以同时拥有一个或多个GUI线程为工作线程处理其他耗时操作。

 

这里抛出一个问题:非GUI线程怎么和GUI线程通讯?

 

可重入(reentrant):如果一个啊哈念书能同事被多个线程调用,并且为每一个调用者提供一份单独的数据,那么称这个函数是可重入的。

线程安全(thread-safe):如果一个函数能同事被多个线程调用,并且所有的调用者引用同一份数据,访问数据时串行处理,那么称这个函数是线程安全的。

 

GUI线程是唯一允许创建QApplication对象并且对它调用exec()函数的线程。在调用了exec()函数后,GUI线程要么等待一个事件,要么处理一个事件。每一个线程都有自己的事件循环。

起始线程通过QCoreApplication::exec()函数启动事件循环,其他非GUI线程通过QThread::exec()启动各自的事件循环。

 

posted on 2015-08-12 14:29  深蓝工作室  阅读(4425)  评论(0编辑  收藏  举报