多线程概述.docx
《多线程概述.docx》由会员分享,可在线阅读,更多相关《多线程概述.docx(16页珍藏版)》请在冰豆网上搜索。
多线程概述
Qt多线程概述
Qt线程类
Qt包含下面一些线程相关的类:
QThread提供了开始一个新线程的方法
QThreadStorage提供逐线程数据存储
QMutex提供相互排斥的锁,或互斥量
QMutexLocker是一个便利类,它可以自动对QMutex加锁与解锁
QReadWriterLock提供了一个可以同时读操作的锁
QReadLocker与QWriteLocker是便利类,它自动对QReadWriteLock加锁与解锁
QSemaphore提供了一个整型信号量,是互斥量的泛化
QWaitCondition提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。
Qt线程的创建
Qt线程中有一个公共的抽象类,所有的线程都是从这个QThread抽象类中派生的,要实现QThread中的纯虚函数run(),run()函数是通过start()函数来实现调用的。
1classMyThread:
publicQThread{
2public3virtualvoidrun();
4};
56voidMyThread:
run()
7{
8forintcount=0;count20;count++){
9sleep
(1);
10qDebug("Ping!
");
11}
12}
1314intmain()
15{
16MyThreada;
17MyThreadb;
1819a.start();//自动调用run(),否则即使该线程创建,也是一开始就挂起
20b.start();
21//要等待线程a,b都退出
22a.wait();
23b.wait();
24}
25Qt线程同步
1.QMutexQMutex(boolrecursive=FALSE)
virtual~QMutex()
voidlock()//试图锁定互斥量。
如果另一个线程已经锁定这个互斥量,那么这次调用将阻塞直到那个线程把它解锁。
voidunlock()
boollocked()
booltryLock()//如果另一个进程已经锁定了这个互斥量,这个函数返回假,而不是一直等到这个锁可用为止,比如,它不是阻塞的。
1//Qt2QMutexmutex;
3voidsomeMethod()
4{
5mutex.lock6qDebug("Hello");
7qDebug("World");
8mutex.unlock();
9}
1011//用Java的术语,这段代码应该是:
12voidsomeMethod()
13{
14synchronized{
15qDebug("Hello");
16qDebug("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.QMutexLockerthiscomplexfunctionlocksaQMutexuponenteringthefunctionandunlocksthemutexatalltheexitpoints1intcomplexFunction(intflag)
2{
3mutex.lock4
5intretVal=0;
67switch(flag){
8case09case110mutex.unlock();
11returnmoreComplexFunction(flag);
12case213{
14intstatus=anotherFunction();
15if(status0){
16mutex.unlock();
17return-2;
18}
19retVal=status+flag;
20}
21break;
22default23if(flag10){
24mutex.unlock();
25return-1;
26}
27break;
28}
2930mutex.unlock();
31returnretVal;
32}
Thisexampleincreasesthelikelihoodthaterrorswilloccur.UsingQMutexLockergreatlysimplifiesthecode,andmakesitmorereadable:
1intcomplexFunction(intflag)
2{
3QMutexLockerlocker(&mutex);
45intretVal=0;
67switch(flag){
8case09case110returnmoreComplexFunction(flag);
11case212{
13intstatus=anotherFunction();
14if(status015return-2;
16retVal=status+flag;
17}
18break;
19default20if(flag1021return-1;
22break;
23}
2425returnretVal;
26}
Now,themutexwillalwaysbeunlockedwhentheQMutexLockerobjectisdestroyed(whenthefunctionreturnssincelockerisanautovariable).即使在抛出异常的情况下也可以使用。
3.QReadWriteLock
用mutex进行线程同步有一个问题就是mutex只允许某个时刻只允许一个线程对共享资源进行访问,如果同时有多个线程对共享
资源进行读访问,而只有一个写操作线程,那么在这种情况下如果采用mutex就成为程序运行性能的瓶颈了。
在这种情况下Qt下采用
QReadWriteLock来实现多个线程读,一个线程写。
写线程执行的时候会阻塞所有的读线程,而读线程之间的运行不需要进行同步。
1MyDatadata;
2QReadWriteLocklock;
3voidReaderThread:
run()
4{
56lock.lockForRead();
7access_data_without_modifying_it(&data);
8lock.unlock();
910}
11voidWriterThread:
run()
12{
1314lock.lockForWrite();
15modify_data(&data);
16lock.unlock();
1718}
1920QReadWriterLock与QMutex相似,除了它对"read","write"访问进行区别对待。
它使得多个读者可以共时访问数据。
使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
4.QReadLocker和QWriteLocker
对于QMutex有QMutexLocker来简化使用,而对于QReadWriteLock有QReadLocker和QWriteLocker。
Here'sanexamplethatusesQReadLockertolockandunlockaread-writelockforreading:
QReadWriteLocklock;
QByteArrayreadData()
{
QReadLockerlocker(&lock);
returndata;
}
Itisequivalenttothefollowingcode:
QReadWriteLocklock;
QByteArrayreadData()
{
lock.lockForRead();
lock.unlock();
returndata;
}
5.QSemaphoreQSemaphore是QMutex的一般化,它可以保护一定数量的相同资源,与此相对,一个mutex只保护一个资源。
下面例子中,使用QSemaphore来控制对环状缓冲区的访问,此缓冲区被生产者线程和消费者线程共享。
生产者不断向缓冲写入数据直到缓冲末端,消费者从缓冲不断从缓冲头部读取数据。
信号量比互斥量有更好的并发性,假如我们用互斥量来控制对缓冲的访问,那么生产者,消费者不能同时访问缓冲。
然而,我们知道在同一时刻,不同线程访问缓冲的不同部分并没有什么危害。
QSemaphoresemaphore
(1);|QMutexmutex;
Qsemaphore.acquire();|Qmutex.lock();
Qsemaphore.release();|Qmutex.unlock();
PublicFunctionsQSemaphore(intn=0)voidacquire(intn=1)intavailable()constvoidrelease(intn=1)booltryAcquire(intn=1)booltryAcquire(intn,inttimeout)Semaphoressupporttwofundamentaloperations,acquire()andreleaseacquire(n)triestoacquirenresources.Iftherearen'tthatmanyresourcesavailable,thecallwillblockuntilthisisthecase.release(n)releasesnresources.returnsimmediatelyifitcannotacquiretheresourcesreturnsthenumberofavailableresourcesatanytime.Example:
QSemaphoresem(5);//sem.available()==5sem.acquire(3);//sem.available()==2sem.acquire
(2);//sem.available()==0sem.release(5);//sem.available()==5sem.release(5);//sem.available()==10sem.tryAcquire
(1);//sem.available()==9,returnstruesem.tryAcquire(250);//sem.available()==9,returnsfalse
生产者线程写数据到buffer直到缓冲末端,然后重新从buffer的头部开始写。
显然producer线程和consumer线程是需要进行同步的,Iftheproducergeneratesthedatatoofast,itwilloverwritedatathattheconsumerhasn'tyetread;iftheconsumerreadsthedatatoofast,itwillpasstheproducerandreadgarbage.
Acrudewaytosolvethisproblemistohavetheproducerfillthebuffer,thenwaituntiltheconsumerhasreadtheentirebuffer,andsoon.显然这样做效率是比较低的。
1constintDataSize=100000;
2constintBufferSize=8192;
3charbuffer[BufferSize];
45//Whentheapplicationstarts,thereaderthreadwillstart
//acquiring"free"bytesandconverttheminto"used"bytes6QSemaphorefreeBytes(BufferSize);//producer线程在此区域写入数据,初始资源数量为BufferSize7QSemaphoreusedBytes;//consumer线程读取此区域的数据,初始资源数量为08
910//Forthisexample,eachbytecountsasoneresource.
11//Inareal-worldapplication,wewouldprobablyoperateonlarger
//units(forexample,64or256bytesatatime)
12classProducer:
publicQThread13{
14public15voidrun();
16};
17//生产者每acquire一次就,使用掉Buffer个资源中的一个,而写入的字符存入到buffer数组中
//从而消费者可用读取字符,从而消费者获取一个资源
18voidProducer:
run()
19{
20//qsrand(QTime(0,0,0).secsTo(QTime:
currentTime()));
21forinti=0;iDataSize;++i){
22freeBytes.acquire();
23buffer[i%BufferSize]="ACGT"int)qrand()%4];
24usedBytes.release();
25}
26}
2728classConsumer:
publicQThread29{
30public31voidrun();
32};
3334voidConsumer:
run()
35{
36forinti=0;iDataSize;++i){
37usedBytes.acquire();
38fprintf(stderr,"%c",buffer[i%BufferSize]);
39freeBytes.release();
40}
41fprintf(stderr,"\n");
42}
43//Finally,inmain(),westarttheproducerandconsumerthreads.
//Whathappensthenisthattheproducerconvertssome"free"space
//into"used"space,andtheconsumercanthenconvertitbackto//"free"space.
46intmain(intargc,char*argv)
47{
48QCoreApplicationapp(argc,argv);
49Producerproducer;
50Consumerconsumer;
51producer.start();
52consumer.start();
53producer.wait();
54consumer.wait();
55return0;
56}producer的run函数:
当producer线程执行run函数,如果buffer中已经满了,而没有consumer线程没有读,这样producer就不能再往buffer
中写字符。
此时在freeBytes.acquire处就阻塞直到consumer线程读(consume)数据。
一旦producer获取到一个字节(资源)
就写如一个随机的字符,并调用usedBytes.release从而consumer线程获取一个资源可以读一个字节的数据了。
consumer的run函数:
当consumer线程执行run函数,如果buffer中没有数据,就是资源=0,则consumer线程在此处阻塞。
直到producer线程执行
写操作,写入一个字节,并执行usedBytes.release从而使得consumer线程的可用资源数=1。
则consumer线程从阻塞状态中退出,
并将usedBytes资源数-1,当前资源数=0。
6.QWaitConditionboolwait(QMutex*mutex,unsignedlongtime=ULONG_MAX)voidwakeOne()voidwakeAll()
Publicfunction:
boolQWaitCondition:
wait(QMutex*mutex,unsignedlongtime=ULONG_MAX)
1)释放锁定的mutex2)在线程对象上等待
mutex必须由调用线程进行初锁定。
注意调用wait的话,会自动调用unlock解锁之前锁住的资源,不然会造成死锁。
线程1等待线程2来改变共享资源,从而达到一定的条件然后发出信号,使得线程1从wait中的阻塞状态中被唤醒。
但是线程2想改变资源,却无法办到,因为线程1调用lock之后就在wait中blocking,了但是没有及时的unlock,那么这就
构成了死锁的条件。
所以说wait函数除了使调用线程切换到内核态之外,还自动unlock(&mutex)
mutex将被解锁,并且调用线程将会阻塞,直到下列条件之一满足时才醒来:
另一个线程使用wakeOne()或wakeAll()传输信号给它。
在这种情况下,这个函数将返回真。
time毫秒过去了。
如果time为ULONG_MAX(默认值),那么这个等待将永远不会超时(这个事件必须被传输)。
如果等待的事件超时,这个函数将会返回假互斥量将以同样的锁定状态返回。
这个函数提供的是允许从锁定状态到等待状态的原子转换。
voidQWaitCondition:
wakeAll()
这将会唤醒所有等待QWaitCondition的线程。
这些线程被唤醒的顺序依赖于操组系统的调度策略,并且不能被控制或预知。
voidQWaitCondition:
wakeOne()
这将会唤醒所有等待QWaitCondition的线程中的一个线程。
这个被唤醒的线程依赖于操组系统的调度策略,并且不能被控制或预知。
假定每次用户按下一个键,我们有三个任务要同时执行,每个任务都可以放到一个线程中,每个线程的run()都应该是这样:
QWaitConditionkey_pressed;
for(;;){
key_pressed.wait();//这是一个QWaitCondition全局变量
//键被按下,做一些有趣的事
do_something();
}
或是这样:
forever{
mutex.lock();
keyPressed.wait(&mutex);
do_something();
mutex.unlock();
}
第四个线程回去读键按下并且每当它接收到一个的时候唤醒其它三个线程,就像这样:
QWaitConditionkey_pressed;
for(;;){
getchar();
//在key_pressed中导致引起任何一个线程。
wait()将会从这个方法中返回并继续执行
key_pressed.wakeAll();
}
注意这三个线程被唤醒的顺序是未定义的,并且当键被按下时,这些线程中的一个或多个还在do_something(),它们将不会被唤醒(因为它们现在没有等待条件变量)并且这个任务也就不会针对这次按键执行操作。
这种情况是可以避免得,比如,就像下面这样做:
1QMutexmymutex;
2QWaitConditionkey_pressed;
3intmycount=0;
45//Worker线程代码
6for(;;){
7key_pressed.wait;//这是一个QWaitCondition全局变量
//keyPressed.wait(&mutex);
8mymutex.lock9mycount++;
10mymutex.unlock();
11do_something();
12mymutex.lock13mycount--;
14mymutex.unlock();
15}
1617//读取按键线程代码
18for(;;){
19getchar();
20mymutex.lock21//睡眠,直到没有忙碌的工作线程才醒来。
count==0说明没有Worker线程在dosomething22while(count0){
23mymutex.unlock();
24sleep
(1);
25mymutex.lock26}
27mymutex.unlock();
28key_pressed.wakeAll();
29}
30应用条件变量对前面用信号量进行保护的环状缓冲区的例子进行改进:
下面的例子中:
1)生产者首先必须检查缓冲是否已满(numUsedBytes==BufferSize),如果是,线程停下来等待bufferNotFull条件。
如果不是,在缓冲中生产数据,增加numUsedBytes,激活条件bufferNotEmpty。
2)使用mutex来保护对numUsedBytes的访问。
另外,QWaitCondition:
wait()接收一个mutex作为参数,这个mutex应该被调用线程初始化为锁定状态。
在线程进入休眠状态之前,mutex会被解锁。
而当线程被唤醒时,mutex会再次处于锁定状态。
而且,从锁定状态到等待状态的转换是原子操作,这阻止了竞争条件的产生。
当程序开始运行时,只有生产者可以工作。
消费者被阻塞等待bufferNotEmpty条件,一旦生产者在缓冲中放入一个字节,bufferNotEmpty条件被激发,消费者线程于是被唤醒。
1constintDataSize=100000;
2constintBufferSize=8192;
3charbuffer[BufferSize];
45QWaitConditionbufferNotEmpty;
6QWaitConditionbufferNotFull;
7QMutexmutex;
8intnumUsedBytes=0;
910classProducer:
publicQThread11{