Qt多线程编程.doc_第1页
Qt多线程编程.doc_第2页
Qt多线程编程.doc_第3页
Qt多线程编程.doc_第4页
Qt多线程编程.doc_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Qt线程类 Qt 包含下面一些线程相关的类:QThread 提供了开始一个新线程的方法QThreadStorage 提供逐线程数据存储QMutex 提供相互排斥的锁,或互斥量QMutexLocker 是一个便利类,它可以自动对QMutex 加锁与解锁QReadWriterLock 提供了一个可以同时读操作的锁QReadLocker 与QWriteLocker 是便利类,它自动对QReadWriteLock 加锁与解锁QSemaphore 提供了一个整型信号量,是互斥量的泛化QWaitCondition 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。Qt线程的创建 Qt线程中有一个公共的抽象类,所有的线程都是从这个QThread抽象类中派生的,要实现QThread中的纯虚函数run(),run()函数是通过start()函数来实现调用的。 1 class MyThread : public QThread 2 public : 3 virtual void run(); 4 ; 5 6 void MyThread:run() 7 8 for ( int count = 0 ; count 20 ; count + ) 9 sleep( 1 );10 qDebug( Ping! );11 12 13 14 int main()15 16 MyThread a;17 MyThread b;18 19 a.start(); / 自动调用run(),否则即使该线程创建,也是一开始就挂起 20 b.start();21 / 要等待线程a,b都退出 22 a.wait();23 b.wait();24 25 Qt线程同步 1.QMutex QMutex ( bool recursive = FALSE )virtual QMutex ()void lock () /试图锁定互斥量。如果另一个线程已经锁定这个互斥量,那么这次调用将阻塞 直到那个线程把它解锁。 void unlock ()bool locked ()bool tryLock () /如果另一个进程已经锁定了这个互斥量,这个函数返回假,而不是一直等到这个锁可用为止,比如,它不是阻塞的。 1 / Qt 2 QMutex mutex; 3 void someMethod() 4 5 mutex. lock (); 6 qDebug( Hello ); 7 qDebug( World ); 8 mutex.unlock(); 9 10 11 / 用Java的术语,这段代码应该是: 12 void someMethod()13 14 synchronized 15 qDebug( Hello );16 qDebug( World );17 18 不过在Qt中我们可用通过另一个类来简化这种应用,因为如果使用QMutex.lock()而没有对应的使用QMutex.unlcok()的话就会造成死锁,别的线程永远也得不到接触该mutex锁住的共享资源的机会。尽管可以不使用lock()而使用tryLock(timeout)来避免因为死等而造成的死锁( tryLock(负值)=lock(),但是还是很有可能造成错误。 对于上述的情况MFC中用CSingleLock 或 MultiLock,Boost中用boost:mutex:scoped_lock来进行解决,而在Qt中用QMutexLocker来进行解决。下面是没有采用 QMutexLocker的例子和采用 QMutexLocker的方案。 2.QMutexLocker this complex function locks a QMutex upon entering the function and unlocks the mutex at all the exit points 1 int complexFunction( int flag) 2 3 mutex. lock (); 4 5 int retVal = 0 ; 6 7 switch (flag) 8 case 0 : 9 case 1 :10 mutex.unlock();11 return moreComplexFunction(flag);12 case 2 :13 14 int status = anotherFunction();15 if (status 10 ) 24 mutex.unlock();25 return - 1 ;26 27 break ;28 29 30 mutex.unlock();31 return retVal;32 This example increases the likelihood that errors will occur.Using QMutexLocker greatly simplifies the code, and makes it more readable: 1 int complexFunction( int flag) 2 3 QMutexLocker locker( & mutex); 4 5 int retVal = 0 ; 6 7 switch (flag) 8 case 0 : 9 case 1 :10 return moreComplexFunction(flag);11 case 2 :12 13 int status = anotherFunction();14 if (status 10 )21 return - 1 ;22 break ;23 24 25 return retVal;26 Now, the mutex will always be unlocked when the QMutexLocker object is destroyed (when the function returns since locker is an auto variable) . 即使在抛出异常的情况下也可以使用。 3.QReadWriteLock 用mutex进行线程同步有一个问题就是mutex只允许某个时刻只允许一个线程对共享资源进行访问,如果同时有多个线程对共享资源进行读访问,而只有一个写操作线程,那么在这种情况下如果采用mutex就成为程序运行性能的瓶颈了。在这种情况下Qt下采用QReadWriteLock来实现多个线程读,一个线程写。写线程执行的时候会阻塞所有的读线程,而读线程之间的运行不需要进行同步。 1 MyData data; 2 QReadWriteLock lock ; 3 void ReaderThread:run() 4 5 6 lock .lockForRead(); 7 access_data_without_modifying_it( & data); 8 lock .unlock(); 9 10 11 void WriterThread:run()12 13 14 lock .lockForWrite();15 modify_data( & data);16 lock .unlock();17 18 19 20 QReadWriterLock 与QMutex相似,除了它对 read,write访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。4.QReadLocker和QWriteLocker 对于QMutex有QMutexLocker来简化使用,而对于QReadWriteLock有 QReadLocker 和 QWriteLocker。 Heres an example that uses QReadLocker to lock and unlock a read-write lock for reading: QReadWriteLock lock ;QByteArray readData() QReadLocker locker(&lock); return data; It is equivalent to the following code: QReadWriteLock lock ; QByteArray readData() lock.lockForRead(); lock.unlock(); return data; 5.QSemaphore QSemaphore 是QMutex 的一般化,它可以保护一定数量的相同资源,与此相对,一个mutex只保护一个资源。下面例子中,使用QSemaphore 来控制对环状缓冲区 的访问,此缓冲区被生产者线程和消费者线程共享。生产者不断向缓冲写入数据直到缓冲末端 ,消费者从缓冲不断从缓冲头部 读取数据。 信号量比互斥量有更好的并发性 ,假如我们用互斥量来控制对缓冲的访问,那么生产者,消费者不能同时访问缓冲 。然而,我们知道在同一时刻,不同线程访问缓冲的不同部分并没有什么危害。 QSemaphore semaphore(1); | QMutex mutex; Qsemaphore.acquire(); | Qmutex.lock(); Qsemaphore.release(); | Qmutex.unlock();Public Functions QSemaphore ( int n = 0 ) QSemaphore () void acquire ( int n = 1 ) int available () const void release ( int n = 1 ) bool tryAcquire ( int n = 1 ) bool tryAcquire ( int n , int timeout ) Semaphores support two fundamental operations, acquire () and release (): acquire(n) tries to acquire n resources. If there arent that many resources available, the call will block until this is the case. release(n ) releases n resources. tryAcquire () returns immediately if it cannot acquire the resources available () returns the number of available resources at any time. Example: QSemaphore sem( 5 ); / sem.available() = 5 sem.acquire( 3 ); / sem.available() = 2 sem.acquire( 2 ); / sem.available() = 0 sem.release( 5 ); / sem.available() = 5 sem.release( 5 ); / sem.available() = 10 sem.tryAcquire( 1 ); / sem.available() = 9, returns true sem.tryAcquire( 250 ); / sem.available() = 9, returns false 生产者线程写数据到buffer直到缓冲末端,然后重新从buffer的头部开始写。 显然producer线程和consumer线程是需要进行同步的,If the producer generates the data too fast, it will overwrite data that the consumer hasnt yet read; if the consumer reads the data too fast, it will pass the producer and read garbage. A crude way to solve this problem is to have the producer fill the buffer, then wait until the consumer has read the entire buffer, and so on. 显然这样做效率是比较低的。 1 const int DataSize = 100000 ; 2 const int BufferSize = 8192 ; 3 char bufferBufferSize; 4 5 / When the application starts, the reader thread will start / acquiring free bytes and convert them into used bytes 6 QSemaphore freeBytes(BufferSize); / producer线程在此区域写入数据 ,初始资源数量为BufferSize 7 QSemaphore usedBytes; / consumer线程读取此区域的数据,初始资源数量为0 8 9 10 / For this example, each byte counts as one resource.11 / In a real-world application, we would probably operate on larger / units (for example, 64 or 256 bytes at a time) 12 class Producer : public QThread13 14 public :15 void run();16 ;17 /生产者每acquire一次就,使用掉Buffer个资源中的一个,而写入的字符存入到buffer数组中 /从而消费者可用读取字符,从而消费者获取一个资源 18 void Producer:run()19 20 /qsrand(QTime(0,0,0).secsTo(QTime:currentTime(); 21 for ( int i = 0 ; i DataSize; + i) 22 freeBytes.acquire ();23 bufferi % BufferSize = ACGT ( int )qrand() % 4 ;24 usedBytes.release ();25 26 27 28 class Consumer : public QThread29 30 public :31 void run();32 ;33 34 void Consumer:run()35 36 for ( int i = 0 ; i 0 ) 23 mymutex.unlock ();24 sleep( 1 );25 mymutex. lock ();26 27 mymutex.unlock ();28 key_pressed.wake All () ;29 30 应用条件变量对前面用信号量进行保护的环状缓冲区的例子进行改进: 下面的例子中: 1)生产者首先必须检查缓冲是否已满(numUsedBytes=BufferSize),如果是,线程停下来等待bufferNotFull条件。如果不是,在缓冲中生产数据,增加numUsedBytes,激活条件 bufferNotEmpty。 2)使用mutex来保护对numUsedBytes的访问。 另外,QWaitCondition:wait ()接收一个mutex作为参数,这个mutex应该被调用线程初始化为锁定状态。在线程进入休眠状态之前,mutex会被解锁。而当线程被唤醒时,mutex会再次处于锁定状态。 而且,从锁定状态到等待状态的转换是原子操作,这阻止了竞争条件的产生。当程序开始运行时,只有生产者可以工作。消费者被阻塞等待 bufferNotEmpty条件,一旦生产者在缓冲中放入一个字节,bufferNotEmpty条件被激发,消费者线程于是被唤醒。 1 const int DataSize = 100000 ; 2 const int BufferSize = 8192 ; 3 char bufferBufferSize; 4 5 QWaitCondition bufferNotEmpty; 6 QWaitCondition bufferNotFull; 7 QMutex mutex; 8 int numUsedBytes = 0 ; 9 10 class Producer : public QThread11 12 public :13 void run();14 ;15 16 void Producer:run()17 18 qsrand(QTime( 0 , 0 , 0 ).secsTo(QTime:currentTime();19 20 for ( int i = 0 ; i DataSize; + i) 21 mutex. lock (); /producer线程首先检查缓冲区是否已满 22 if (numUsedBytes = BufferSize)/缓冲区已满,等待consumer来减少numUsedBytes / bufferNotFull.wait(&mutex)先调用 mutex.unlock ()然后收到信号时调用 mutex. lock () 23 bufferNotFull.wait( & mutex);/缓冲区已满等待bufferNotFull的条件变量成立变为有信号 24 mutex.unlock ();25 26 bufferi % BufferSize = ACGT ( int )qrand() % 4 ;27 28 mutex. lock ();29 + numUsedBytes; /producer用掉一个Bytes,表示producer写入buffer中的字节数 30 bufferNotEmpty.wakeAll();31 mutex.unlock ();32 33 34 35 class Consumer : public QThread36 37 public :38 void run();39 ;40 41 void Consumer:run()42 43 for ( int i = 0 ; i DataSize; + i) 44 mutex. lock ();45 if (numUsedBytes = 0 )46 bufferNotEmpty.wait( & mutex);47 mutex.unlock ();48 49 fprintf(stderr, %c , bufferi % BufferSize);50 51 mutex. lock ();52 - numUsedBytes;53 bufferNotFull.wakeAll();54 mutex.unlock ();55 56 fprintf(stderr, n );57 58 59 int main( int argc, char * argv)60 61 QCoreApplication app(argc, argv);62 Producer producer;63 Consumer consumer;64 producer.start();65 consumer.start();66 producer.wait();67 consumer.wait();68 return 0 ;69 另外一个例子: 1 #include 2 #include 3 4 / 全局条件变量 5 QWaitCondition mycond; 6 7 / Worker类实现 8 class Worker : public QPushButton, public QThread 9 10 Q_OBJECT11 12 public :13 Worker(QWidget * parent = 0 , const char * name = 0 )14 : QPushButton(parent, name)15 16 setText( Start Working );17 18 / 连接从QPushButton继承来的信号和我们的slotClicked()方法 19 connect( this , SIGNAL(clicked(), SLOT(slotClicked();20 21 / 调用从QThread继承来的start()方法这将立即开始线程的执行 22 QThread:start();23 24 25 public slots:26 void slotClicked()27 28 / 唤醒等待这个条件变量的一个线程 29 mycond.wakeOne ();30 31 32 protected :33 void run()34 35 / 这个方法将被新创建的线程调用 36 37 while ( TRUE ) 38 / 锁定应用程序互斥锁,并且设置窗口标题来表明我们正在等待开始工作 39 qApp - lock ();40 setCaption( Waiting );41 qApp - unlock ();42 43 / 等待直到我们被告知可以继续 44 mycond.wait ();45 46 / 如果我们到了这里,我们已经被另一个线程唤醒让我们来设置标题来表明我们正在工作 47 qApp - lock ();48 setCaption( Working! );49 qApp - unlock ();50 51 / 这可能会占用一些时间,几秒、几分钟或者几小时等等,因为这个一个和GUI线程分开的线程,在处理事件时,GUI线程不会停下来 52 do_complicated_thing();53 54 55 ;56 57 / 主线程所有的GUI事件都由这个线程处理。 58 int main( int argc, char * argv )59 60 QApplication app( argc, argv );61 62 / 创建一个worker当我们这样做的时候,这个worker将在一个线程中运行 63 Worker firstworker( 0 , worker );64 65 app.setMainWidget( & worker );66 worker.show();67 68 return app.exec();69 70 7.线程安全类 非线程安全类 下面这个类不是线程安全的,因为假如多个线程都试图修改数据成员 n,结果未定义。这是因为c+中的+和-操作符不是原子操作。实际上,它们会被扩展为三个机器指令:1,把变量值装入寄存器2,增加或减少寄存器中的值3,把寄存器中的值写回内存 假如线程A与B同时装载变量的旧值,在寄存器中增值,回写。他们写操作重叠了,导致变量值仅增加了一次。很明显,访问应该串行化:A执行123步骤时不应被打断。class Counter public : Counter() n = 0 ; void increment() + n; void decrement() - n; int value() const return n; private : int 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 mutex; int n; ; QMutexLocker类在构造函数中自动对mutex进行加锁,在析构函数中进行解锁。随便一提的是,mutex使用了mutable 关键字来修饰,因为我们在value()函数中对mutex进行加锁与解锁操作,而value ()是一个const 函数。可重入与线程安全在Qt文档中,术语“可重入 ”与“线程安全 ”被用来说明一个函数如何用于多线程程序。假如一个类的任何函数在此类的多个不同的实例上,可以被多个线程同时调用,那么这个类被称为是“可重入”的。假如不同的线程作用在同一个实例上仍可以正常工作,那么称之为“线程安全”的。大多数c+类天生就是可重入的,因为它们典型地仅仅引用成员数据。任何线程可以在类的一个实例上调用这样的成员函数,只要没有别的线程在同一个实例上调用这个成员函数。举例来讲,下面的Counter 类是可重入的:class Counter public : Counter() n=0; void increment() +n; void decrement() -n; int value() const return n; private : int n;这个类不是线程安全的,因为假如多个线程都试图修改数据成员 n,结果未定义。这是因为c+中的+和-操作符不是原子操作。实际上,它们会被扩展为三个机器指令:1,把变量值装入寄存器2,增加或减少寄存器中的值3,把寄存器中的值写回内存 假如线程A与B同时装载变量的旧值,在寄存器中增值,回写。他们写操作重叠了,导致变量值仅增加了一次。很明显,访问应该串行化:A执行123步骤时不应被打断。使这个类成为线程安全的最简单方法是使用QMutex 来保护数据成员:class Counter public : Counter() n = 0; void increment() QMutexLocker locker(&mutex); +n; void decrement() QMutexLocker locker(&

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论