黑马程序员C语言教程Qt多线程程序设计.docx

上传人:b****7 文档编号:8827744 上传时间:2023-02-02 格式:DOCX 页数:8 大小:21.38KB
下载 相关 举报
黑马程序员C语言教程Qt多线程程序设计.docx_第1页
第1页 / 共8页
黑马程序员C语言教程Qt多线程程序设计.docx_第2页
第2页 / 共8页
黑马程序员C语言教程Qt多线程程序设计.docx_第3页
第3页 / 共8页
黑马程序员C语言教程Qt多线程程序设计.docx_第4页
第4页 / 共8页
黑马程序员C语言教程Qt多线程程序设计.docx_第5页
第5页 / 共8页
点击查看更多>>
下载资源
资源描述

黑马程序员C语言教程Qt多线程程序设计.docx

《黑马程序员C语言教程Qt多线程程序设计.docx》由会员分享,可在线阅读,更多相关《黑马程序员C语言教程Qt多线程程序设计.docx(8页珍藏版)》请在冰豆网上搜索。

黑马程序员C语言教程Qt多线程程序设计.docx

黑马程序员C语言教程Qt多线程程序设计

传智播客C/C++培训专家:

Qt多线程程序设计

分类:

 C/C++

QT通过三种形式提供了对线程支持。

它们分别是,一、平台无关线程类,二、线程安全事件投递,三、跨线程信号-槽连接。

这使得开发轻巧多线程Qt程序更为容易,并能充分运用多解决器机器优势。

多线程编程也是一种有用模式,它用于解决执行较长时间操作而不至于顾客界面失去响应。

在Qt初期版本中,在构建库时有不选取线程支持选项,从4.0开始,线程总是有效。

线程类

Qt 包括下面某些线程有关类:

QThread 提供了开始一种新线程办法

QThreadStorage 提供逐线程数据存储

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

QMutexLocker 是一种便利类,它可以自动对QMutex加锁与解锁

QReadWriterLock 提供了一种可以同步读操作锁

QReadLocker与QWriteLocker 是便利类,它自动对QReadWriteLock加锁与解锁

QSemaphore 提供了一种整型信号量,是互斥量泛化

QWaitCondition 提供了一种办法,使得线程可以在被此外线程唤醒之前始终休眠。

创立一种线程

为创立一种线程,子类化QThread并且重写它run()函数,例如:

classMyThread:

publicQThread

{

Q_OBJECT

protected:

voidrun();

};

voidMyThread:

:

run()

{

...

}

创立这个线程对象实例,调用QThread:

:

start()。

于是,在run()里出当代码将会在此外线程中被执行。

注意:

QCoreApplication:

:

exec()必要总是在主线程(执行main()那个线程)中被调用,不能从一种QThread中调用。

在GUI程序中,主线程也被称为GUI线程,由于它是唯一一种容许执行GUI有关操作线程。

此外,你必要在创立一种QThread之前创立QApplication(orQCoreApplication)对象。

 

线程同步

QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了线程同步手段。

使用线程重要想法是但愿它们可以尽量并发执行,而某些核心点上线程之间需要停止或等待。

例如,如果两个线程试图同步访问同一种全局变量,成果也许不如所愿。

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

在一种时刻至多一种线程拥有mutex,如果一种线程试图访问已经被锁定mutex,那么它将休眠,直到拥有mutex线程对此mutex解锁。

Mutexes惯用来保护共享数据访问。

QReadWriterLock 与QMutex相似,除了它对"read","write"访问进行区别对待。

它使得各种读者可以共时访问数据。

使用QReadWriteLock而不是QMutex,可以使得多线程程序更具备并发性。

QReadWriteLocklock;

voidReaderThread:

:

run()

{

//...

lock.lockForRead();

read_file();

lock.unlock();

//...

}

voidWriterThread:

:

run()

{

//...

lock.lockForWrite();

write_file();

lock.unlock();

//...

}

QSemaphore 是QMutex普通化,它可以保护一定数量相似资源,与此相对,一种mutex只保护一种资源。

下面例子中,使用QSemaphore来控制对环状缓冲访问,此缓冲区被生产者线程和消费者线程共享。

生产者不断向缓冲写入数据直到缓冲末端,再从头开始。

消费者从缓冲不断读取数据。

信号量比互斥量有更好并发性,如果咱们用互斥量来控制对缓冲访问,那么生产者,消费者不能同步访问缓冲。

然而,咱们懂得在同一时刻,不同线程访问缓冲不同某些并没有什么危害。

constintDataSize=100000;

constintBufferSize=8192;

charbuffer[BufferSize];

QSemaphorefreeBytes(BufferSize);

QSemaphoreusedBytes;

classProducer:

publicQThread

{

public:

voidrun();

};

voidProducer:

:

run()

{

qsrand(QTime(0,0,0).secsTo(QTime:

:

currentTime()));

for(inti=0;i

freeBytes.acquire();

buffer[i%BufferSize]="ACGT"[(int)qrand()%4];

usedBytes.release();

}

}

classConsumer:

publicQThread

{

public:

voidrun();

};

voidConsumer:

:

run()

{

for(inti=0;i

usedBytes.acquire();

fprintf(stderr,"%c",buffer[i%BufferSize]);

freeBytes.release();

}

fprintf(stderr,"\n");

}

intmain(intargc,char*argv[])

{

QCoreApplicationapp(argc,argv);

Producerproducer;

Consumerconsumer;

producer.start();

consumer.start();

producer.wait();

consumer.wait();

return0;

}

QWaitCondition 容许线程在某些状况发生时唤醒此外线程。

一种或各种线程可以阻塞等待一QWaitCondition,用wakeOne()或wakeAll()设立一种条件。

wakeOne()随机唤醒一种,wakeAll()唤醒所有。

下面例子中,生产者一方面必要检查缓冲与否已满(numUsedBytes==BufferSize),如果是,线程停下来等待bufferNotFull条件。

如果不是,在缓冲中生产数据,增长numUsedBytes,激活条件bufferNotEmpty。

使用mutex来保护对numUsedBytes访问。

此外,QWaitCondition:

:

wait()接受一种mutex作为参数,这个mutex应当被调用线程初始化为锁定状态。

在线程进入休眠状态之前,mutex会被解锁。

而当线程被唤醒时,mutex会处在锁定状态,并且,从锁定状态到等待状态转换是原子操作,这制止了竞争条件产生。

当程序开始运营时,只有生产者可以工作。

消费者被阻塞等待bufferNotEmpty条件,一旦生产者在缓冲中放入一种字节,bufferNotEmpty条件被激发,消费者线程于是被唤醒。

constintDataSize=100000;

constintBufferSize=8192;

charbuffer[BufferSize];

QWaitConditionbufferNotEmpty;

QWaitConditionbufferNotFull;

QMutexmutex;

intnumUsedBytes=0;

classProducer:

publicQThread

{

public:

voidrun();

};

voidProducer:

:

run()

{

qsrand(QTime(0,0,0).secsTo(QTime:

:

currentTime()));

for(inti=0;i

mutex.lock();

if(numUsedBytes==BufferSize)

bufferNotFull.wait(&mutex);

mutex.unlock();

buffer[i%BufferSize]="ACGT"[(int)qrand()%4];

mutex.lock();

++numUsedBytes;

bufferNotEmpty.wakeAll();

mutex.unlock();

}

}

classConsumer:

publicQThread

{

public:

voidrun();

};

voidConsumer:

:

run()

{

for(inti=0;i

mutex.lock();

if(numUsedBytes==0)

bufferNotEmpty.wait(&mutex);

mutex.unlock();

fprintf(stderr,"%c",buffer[i%BufferSize]);

mutex.lock();

--numUsedBytes;

bufferNotFull.wakeAll();

mutex.unlock();

}

fprintf(stderr,"\n");

}

intmain(intargc,char*argv[])

{

QCoreApplicationapp(argc,argv);

Producerproducer;

Consumerconsumer;

producer.start();

consumer.start();

producer.wait();

consumer.wait();

return0;

}

 

可重入与线程安全

在Qt文档中,术语“可重入”与“线程安全”被用来阐明一种函数如何用于多线程程序。

如果一种类任何函数在此类各种不同实例上,可以被各种线程同步调用,那么这个类被称为是“可重入”。

如果不同线程作用在同一种实例上仍可以正常工作,那么称之为“线程安全”。

大多数c++类天生就是可重入,由于它们典型地仅仅引用成员数据。

任何线程可以在类一种实例上调用这样成员函数,只要没有别线程在同一种实例上调用这个成员函数。

举例来讲,下面Counter类是可重入:

classCounter

{

public:

Counter(){n=0;}

voidincrement(){++n;}

voiddecrement(){--n;}

intvalue()const{returnn;}

private:

intn;

};

这个类不是线程安全,由于如果各种线程都试图修改数据成员n,成果未定义。

这是由于c++中++和--操作符不是原子操作。

事实上,它们会被扩展为三个机器指令:

1,把变量值装入寄存器

2,增长或减少寄存器中值

3,把寄存器中值写回内存

如果线程A与B同步装载变量旧值,在寄存器中增值,回写。

她们写操作重叠了,导致变量值仅增长了一次。

很明显,访问应当串行化:

A执行123环节时不应被打断。

使这个类成为线程安全最简朴办法是使用QMutex来保护数据成员:

classCounter

{

public:

Counter(){n=0;}

voidincrement(){QMutexLockerlocker(&mutex);++n;}

voiddecrement(){QMutexLockerlocker(&mutex);--n;}

intvalue()const{QMutexLockerlocker(&mutex);returnn;}

private:

mutableQMutexmutex;

intn;

};

QMutexLocker类在构造函数中自动对mutex进行加锁,在析构函数中进行解锁。

随便一提是,mutex使用了mutable核心字来修饰,由于咱们在value()函数中对mutex进行加锁与解锁操作,而value()是一种const函数。

大多数Qt类是可重入,非线程安全。

有某些类与函数是线程安全,它们重要是线程有关类,如QMutex,QCoreApplication:

:

postEvent()。

 

线程与QObjects

QThread 继承自QObject,它发射信号以批示线程执行开始与结束,并且也提供了许多slots。

更有趣是,QObjects可以用于多线程,这是由于每个线程被容许有它自己事件循环。

QObject是可重入。

它大多数非GUI子类,像QTimer,QTcpSocket,QUdpSocket,QHttp,QFtp,QProcess也是可重入,在各种线程中同步使用这些类是也许。

需要注意是,这些类被设计成在一种单线程中创立与使用,因而,在一种线程中创立一种对象,而在此外线程中调用它函数,这样行为不能保证工作良好。

有三种约束需要注意:

1,QObject孩子总是应当在它爸爸被创立那个线程中创立。

这意味着,你绝不应当传递QThread对象作为另一种对象爸爸(由于QThread对象自身会在另一种线程中被创立)

2,事件驱动对象仅仅在单线程中使用。

明确地说,这个规则合用于"定期器机制“与”网格模块“,举例来讲,你不应当在一种线程中开始一种定期器或是连接一种套接字,当这个线程不是这些对象所在线程。

3,你必要保证在线程中创立所有对象在你删除QThread前被删除。

这很容易做到:

你可以run()函数运营栈上创立对象。

尽管QObject是可重入,但GUI类,特别是QWidget与它所有子类都是不可重入。

它们仅用于主线程。

正如前面提到过,QCoreApplication:

:

exec()也必要从那个线程中被调用。

实践上,不会在别线程中使用GUI类,它们工作在主线程上,把某些耗时操作放入独立工作线程中,当工作线程运营完毕,把成果在主线程所拥有屏幕上显示。

逐线程事件循环

每个线程可以有它事件循环,初始线程开始它事件循环需使用QCoreApplication:

:

exec(),别线程开始它事件循环需要用QThread:

:

exec().像QCoreApplication同样,QThreadr提供了exit(int)函数,一种quit()slot。

线程中事件循环,使得线程可以使用那些需要事件循环非GUI类(如,QTimer,QTcpSocket,QProcess)。

也可以把任何线程signals连接到特定线程slots,也就是说信号-槽机制是可以跨线程使用。

对于在QApplication之前创立对象,QObject:

:

thread()返回0,这意味着主线程仅为这些对象解决投递事件,不会为没有所属线程对象解决此外事件。

可以用QObject:

:

moveToThread()来变化它和它孩子们线程亲缘关系,如果对象有爸爸,它不能移动这种关系。

在另一种线程(而不是创立它那个线程)中delete QObject对象是不安全。

除非你可以保证在同一时刻对象不在解决事件。

可以用QObject:

:

deleteLater(),它会投递一种DeferredDelete事件,这会被对象线程事件循环最后选用到。

如果没有事件循环运营,事件不会分发给对象。

举例来说,如果你在一种线程中创立了一种QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它timeout()信号.对deleteLater()也不会工作。

(这同样合用于主线程)。

你可以手工使用线程安全函数QCoreApplication:

:

postEvent(),在任何时候,给任何线程中任何对象投递一种事件,事件会在那个创立了对象线程中通过事件循环派发。

事件过滤器在所有线程中也被支持,但是它限定被监视对象与监视对象生存在同一线程中。

类似地,QCoreApplication:

:

sendEvent(不是postEvent()),仅用于在调用此函数线程中向目的对象投递事件。

从别线程中访问QObject子类

QObject和所有它子类是非线程安全。

这涉及整个事件投递系统。

需要紧记是,当你正从别线程中访问对象时,事件循环可以向你QObject子类投递事件。

如果你调用一种不生存在当前线程中QObject子类函数时,你必要用mutex来保护QObject子类内部数据,否则会遭遇劫难或非预期成果。

像其他对象同样,QThread对象生存在创立它那个线程中---不是当QThread:

:

run()被调用时创立那个线程。

普通来讲,在你QThread子类中提供slots是不安全,除非你用mutex保护了你成员变量。

另一方面,你可以安全从QThread:

:

run()实现中发射信号,由于信号发射是线程安全。

跨线程信号-槽

Qt支持三种类型信号-槽连接:

1,直接连接,当signal发射时,slot及时调用。

此slot在发射signal那个线程中被执行(不一定是接受对象生存那个线程)

2,队列连接,当控制权回到对象属于那个线程事件循环时,slot被调用。

此slot在接受对象生存那个线程中被执行

3,自动连接(缺省),如果信号发射与接受者在同一种线程中,其行为如直接连接,否则,其行为如队列连接。

连接类型也许通过以向connect()传递参数来指定。

注意是,当发送者与接受者生存在不同线程中,而事件循环正运营于接受者线程中,使用直接连接是不安全。

同样道理,调用生存在不同线程中对象函数也是不是安全。

QObject:

:

connect()自身是线程安全。

多线程与隐含共享

Qt为它许多值类型使用了所谓隐含共享(implicitsharing)来优化性能。

原理比较简朴,共享类包括一种指向共享数据块指针,这个数据块中包括了真正原数据与一种引用计数。

把深拷贝转化为一种浅拷贝,从而提高了性能。

这种机制在幕后发生作用,程序员不需要关怀它。

如果进一步点看,如果对象需要对数据进行修改,而引用计数不不大于1,那么它应当先detach()。

以使得它修改不会对别共享者产生影响,既然修改后数据与本来那份数据不同了,因而不也许再共享了,于是它先执行深拷贝,把数据取回来,再在这份数据上进行修改。

例如:

voidQPen:

:

setStyle(Qt:

:

PenStylestyle)

{

detach();//detachfromcommondata

d->style=style;//setthestylemember

}

voidQPen:

:

detach()

{

if(d->ref!

=1){

...//performadeepcopy

}

}

普通以为,隐含共享与多线程不太和谐,由于有引用计数存在。

对引用计数进行保护办法之一是使用mutex,但它很慢,Qt初期版本没有提供一种满意解决方案。

从4.0开始,隐含共享类可以安全地跨线程拷贝,犹如别值类型同样。

它们是完全可重入。

隐含共享真是"implicit"。

它使用汇编语言实现了原子性引用计数操作,这比用mutex快多了。

如果你在各种线程中同进访问相似对象,你也需要用mutex来串行化访问顺序,就犹如其她可重入对象那样。

总来讲,隐含共享真给”隐含“掉了,在多线程程序中,你可以把它们当作是普通,非共享,可重入类型,这种做法是安全。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 工学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1