QT开发(三十四)——QT多线程编程

QT开发(三十四)——QT多线程编程

一、QT多线程简介

QT通过三种形式提供了对线程的支持,分别是平台无关的线程类、线程安全的事件投递、跨线程的信号-槽连接。

QT中线程类包含如下:

QThread 提供了开始一个新线程的方法
    QThreadStorage 提供逐线程数据存储
    QMutex 提供相互排斥的锁,或互斥量
    QMutexLocker 是一个辅助类,自动对 QMutex 加锁与解锁
    QReadWriterLock 提供了一个可以同时读操作的锁
    QReadLocker与QWriteLocker 自动对QReadWriteLock 加锁与解锁
    QSemaphore 提供了一个整型信号量,是互斥量的泛化
    QWaitCondition 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。

QThread的两种使用方法:

A、不使用事件循环。

a. 子类化 QThread

b. 重载 run 函数,run函数内有一个 while 或 for 的死循环

c. 设置一个标记为来控制死循环的退出。

B、使用事件循环。

a. 子类化 QThread,

b. 重载 run 使其调用 QThread::exec()

c. 为子类定义信号和槽,由于槽函数并不会在新开的 thread 运行,很多人为了解决这个问题在构造函数中调用 moveToThread(this)
Bradley T. Hughes 给出说明是: QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码。需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。

在Qt4.4之前,run 是虚函数,必须子类化QThread来实现run函数。
而从Qt4.4开始,QThread不再支持抽象类,run 默认调用 QThread::exec() ,不需要子类化 QThread 了,只需要子类化一个 QObject 。

二、QThread线程

QThread是Qt线程中有一个公共的抽象类,所有的线程都是从QThread抽象类中派生的,要实现QThread中的纯虚函数run(),run()函数是通过start()函数来实现调用的。

创建线程对象的实例,调用QThread::start(),在子线程类run()里出现的代码将会在新建线程中被执行。
    QCoreApplication::exec()总是在主线程(执行main()的那个线程)中被调用,不能从一个QThread中调用。在GUI程序中,主线程也被称为GUI线程,是唯一允许执行GUI相关操作的线程。另外,必须在创建一个QThread之前创建QApplication(or QCoreApplication)对象。

1、线程的优先级

QThread线程总共有8个优先级

QThread::IdlePriority0scheduled only when no other threads are running.

QThread::LowestPriority1scheduled less often than LowPriority.

QThread::LowPriority2scheduled less often than NormalPriority.

QThread::NormalPriority3the default priority of the operating system.

QThread::HighPriority4scheduled more often than NormalPriority.

QThread::HighestPriority5scheduled more often than HighPriority.

QThread::TimeCriticalPriority6scheduled as often as possible.

QThread::InheritPriority7use the same priority as the creating thread. This is the default.

2、线程的创建

void start ( Priority priority = InheritPriority )

启动线程执行,启动后会发出started ()信号

3、线程的执行

int exec();

进入线程时间循环

virtual void run();

线程入口

4、线程的退出

void quit();

相当于exit(0);

void exit ( int returnCode = 0 );

调用exit后,thread将退出event loop,并从exec返回,exec的返回值就是returnCode。通常returnCode=0表示成功,其他值表示失败。

void terminate ();

结束线程,线程是否立即终止取决于操作系统。

线程被终止时,所有等待该线程Finished的线程都将被唤醒。

terminate是否调用取决于setTerminationEnabled ( bool enabled = true )开关。

5、线程的等待

void msleep ( unsigned long msecs )

void sleep ( unsigned long secs )

void usleep ( unsigned long usecs )

bool wait ( unsigned long time = ULONG_MAX )

线程将会被阻塞,等待time毫秒,如果线程退出,则wait会返回。

6、线程的状态

bool isFinished () const线程是否已经退出

bool isRunning () const线程是否处于运行状态

7、线程的属性

Priority priority () const

void setPriority ( Priority priority )

uint stackSize () const

void setStackSize ( uint stackSize )

void setTerminationEnabled ( bool enabled = true )

设置是否响应terminate()函数

8、线程与事件循环

QThread中对run()的默认实现调用了exec(),从而创建一个QEventLoop对象,由QEventLoop对象处理线程中事件队列(每一个线程都有一个属于自己的事件队列)中的事件。exec()在其内部不断做着循环遍历事件队列的工作,调用QThread的quit()或exit()方法使退出线程,尽量不要使用terminate()退出线程,terminate()退出线程过于粗暴,造成资源不能释放,甚至互斥锁还处于加锁状态。

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

在QApplication之前创建的对象,QObject::thread()返回0,这意味着主线程仅为这些对象处理投递事件,不会为没有所属线程的对象处理另外的事件。可以用QObject::moveToThread()来改变它和它孩子们的线程亲缘关系,假如对象有父亲,它不能移动这种关系。在另一个线程(而不是创建它的那个线程)中delete QObject对象是不安全的。除非你可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。假如没有事件循环运行,事件不会分发给对象。举例来说,假如你在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号.对deleteLater()也不会工作。(这同样适用于主线程)。你可以手工使用线程安全的函数QCoreApplication::postEvent(),在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事件循环派发。事件过滤器在所有线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。类似地,QCoreApplication::sendEvent(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。

三、线程的同步

QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了线程同步的手段。使用线程的主要想法是希望它们可以尽可能并发执行,而一些关键点上线程之间需要停止或等待。例如,假如两个线程试图同时访问同一个 全局变量,结果可能不如所愿。

1、互斥量QMutex

QMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么它将休眠,直到拥有mutex的线程对此mutex解锁。QMutex常用来保护共享数据访问。QMutex类所以成员函数是线程安全的。

头文件声明:    #include <QMutex>

互斥量声明:    QMutex m_Mutex;

互斥量加锁:    m_Mutex.lock();

互斥量解锁:    m_Mutex.unlock();

QMutex ( RecursionMode mode = NonRecursive )

QMutex有两种模式:Recursive, NonRecursive

A、Recursive

一个线程可以对mutex多次lock,直到相应次数的unlock调用后,mutex才真正被解锁。

B、NonRecursive

默认模式,mutex只能被lock一次。

如果使用了Mutex.lock()而没有对应的使用Mutex.unlcok()的话就会造成死锁,其他的线程将永远也得不到接触Mutex锁住的共享资源的机会。尽管可以不使用lock()而使用tryLock(timeout)来避免因为死等而造成的死锁( tryLock(负值)==lock()),但是还是很有可能造成错误。

bool tryLock();

如果当前其他线程已对该mutex加锁,则该调用会立即返回,而不被阻塞。

bool tryLock(int timeout);

如果当前其他线程已对该mutex加锁,则该调用会等待一段时间,直到超时

QMutex mutex;
int complexFunction(int flag)
 {
     mutex.lock();
     int retVal = 0;
     switch (flag) {
     case 0:
     case 1:
         mutex.unlock();
         return moreComplexFunction(flag);
     case 2:
         {
             int status = anotherFunction();
             if (status < 0) {
                 mutex.unlock();
                 return -2;
             }
             retVal = status + flag;
         }
         break;
     default:
         if (flag > 10) {
             mutex.unlock();
             return -1;
         }
         break;
     }
 
     mutex.unlock();
     return retVal;
 }

2、互斥锁QMutexLocker

在较复杂的函数和异常处理中对QMutex类mutex对象进行lock()和unlock()操作将会很复杂,进入点要lock(),在所有跳出点都要unlock(),很容易出现在某些跳出点未调用unlock(),所以Qt引进了QMutex的辅助类QMutexLocker来避免lock()和unlock()操作。在函数需要的地方建立QMutexLocker对象,并把mutex指针传给QMutexLocker对象,此时mutex已经加锁,等到退出函数后,QMutexLocker对象局部变量会自己销毁,此时mutex解锁。

头文件声明:    #include<QMutexLocker>

互斥锁声明:    QMutexLocker mutexLocker(&m_Mutex);

互斥锁加锁:    从声明处开始(在构造函数中加锁)

互斥锁解锁:    出了作用域自动解锁(在析构函数中解锁)

QMutex mutex;
 int complexFunction(int flag)
 {
     QMutexLocker locker(&mutex);
     int retVal = 0;
     switch (flag) {
     case 0:
     case 1:
         return moreComplexFunction(flag);
     case 2:
         {
             int status = anotherFunction();
             if (status < 0)
                 return -2;
             retVal = status + flag;
         }
         break;
     default:
         if (flag > 10)
             return -1;
         break;
     }
     return retVal;
 }

3、QReadWriteLock

QReadWriterLock 与QMutex相似,但对读写操作访问进行区别对待,可以允许多个读者同时读数据,但只能有一个写,并且写读操作不同同时进行。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。QReadWriterLock默认模式是NonRecursive。

QReadWriterLock类成员函数如下:

QReadWriteLock ( )

QReadWriteLock ( RecursionMode recursionMode )

void lockForRead ()

void lockForWrite ()

bool tryLockForRead ()

bool tryLockForRead ( int timeout )

bool tryLockForWrite ()

bool tryLockForWrite ( int timeout )

boid unlock ()

使用实例:

QReadWriteLock lock;
 void ReaderThread::run()
 {
     lock.lockForRead();
     read_file();
     lock.unlock();
 }
 
 void WriterThread::run()
 {
     lock.lockForWrite();
     write_file();
     lock.unlock();
 }

4、QReadLocker和QWriteLocker

在较复杂的函数和异常处理中对QReadWriterLock类lock对象进行lockForRead()/lockForWrite()和unlock()操作将会很复杂,进入点要lockForRead()/lockForWrite(),在所有跳出点都要unlock(),很容易出现在某些跳出点未调用unlock(),所以Qt引进了QReadLocker和QWriteLocker类来简化解锁操作。在函数需要的地方建立QReadLocker或QWriteLocker对象,并把lock指针传给QReadLocker或QWriteLocker对象,此时lock已经加锁,等到退出函数后,QReadLocker或QWriteLocker对象局部变量会自己销毁,此时lock解锁。

QReadWriteLock lock;

QByteArray readData()

{

lock.lockForRead();

...

lock.unlock();

return data;

}

使用QReadLocker:

QReadWriteLock lock;

QByteArray readData()

{

QReadLocker locker(&lock);

...

return data;

}

5、信号量QSemaphore

QSemaphore 是QMutex的一般化,可以保护一定数量的相同资源,而一个mutex只保护一个资源。QSemaphore 类的所有成员函数是线程安全的。

经典的生产者-消费者模型如下:某工厂只有固定仓位,生产人员每天生产的产品数量不一,销售人员每天销售的产品数量也不一致。当生产人员生产P个产品时,就一次需要P个仓位,当销售人员销售C个产品时,就要求仓库中有足够多的产品才能销售。如果剩余仓位没有P个时,该批次的产品都不存入,当当前已有的产品没有C个时,就不能销售C个以上的产品,直到新产品加入后方可销售。

QSemaphore来控制对环状缓冲的访问,此缓冲区被生产者线程和消费者线程共享。生产者不断向缓冲区写入数据直到缓冲末端,再从头开始。消费者从缓冲不断读取数据。信号量比互斥量有更好的并发性,假如我们用互斥量来控制对缓冲的访问,那么生产者、消费者不能同时访问缓冲区。然而,我们知道在同一时刻,不同线程访问缓冲的不同部分并没有什么危害。

QSemaphore 类成员函数:

QSemaphore ( int n = 0 )

void acquire ( int n = 1 )

int available () const

void release ( int n = 1 )

bool tryAcquire ( int n = 1 )

bool tryAcquire ( int n, int timeout )

实例代码:

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

生产者-消费者实例:

#include <QtCore/QCoreApplication>
#include <QSemaphore>
#include <QThread>
#include <cstdlib>
#include <cstdio>
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QSemaphore  production(BufferSize);
QSemaphore  consumption;
class Producor:public QThread
{
public:
    void run();
};
void Producor::run()
{
    for(int i = 0; i < DataSize; i++)
    {
        production.acquire();
        buffer[i%BufferSize] = "ACGT"[(int)qrand()%4];
        consumption.release();
    }
}
class Consumer:public QThread
{
public:
    void run();
};
void Consumer::run()
{
    for(int i = 0; i < DataSize; i++)
    {
        consumption.acquire();
        fprintf(stderr, "%c", buffer[i%BufferSize]);
        production.release();
    }
    fprintf(stderr, "%c", "\n");
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Producor productor;
    Consumer consumer;
    productor.start();
    consumer.start();
    productor.wait();
    consumer.wait();
    return a.exec();
}

Producer::run函数:

当producer线程执行run函数,如果buffer中已满,而consumer线程没有读,producer不能再往buffer中写字符,在 productor.acquire 处阻塞直到 consumer线程读(consume)数据。一旦producer获取到一个字节(资源)就写入一个随机的字符,并调用 consumer.release 使consumer线程可以获取一个资源(读一个字节的数据)。

Consumer::run函数:

当consumer线程执行run函数,如果buffer中没有数据,则consumer线程在consumer.acquire处阻塞,直到producer线程执行写操作写入一个字节,并执行consumer.release 使consumer线程的可用资源数=1时,consumer线程从阻塞状态中退出, 并将consumer 资源数-1,consumer当前资源数=0。

6、等待条件QWaitCondition

QWaitCondition 允许线程在某些情况发生时唤醒另外的线程。一个或多个线程可以阻塞等待QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒所有。

QWaitCondition ()

bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )

bool wait ( QReadWriteLock * readWriteLock, unsigned long time = ULONG_MAX )

void wakeOne ()

void wakeAll ()

头文件声明:    #include <QWaitCondition>

等待条件声明:    QWaitCondtion m_WaitCondition;

等待条件等待:    m_WaitConditon.wait(&m_muxtex, time);

等待条件唤醒:    m_WaitCondition.wakeAll();

在经典的生产者-消费者场合中,生产者首先必须检查缓冲是否已满(numUsedBytes==BufferSize),如果缓冲区已满,线程停下来等待 bufferNotFull条件。如果没有满,在缓冲中生产数据,增加numUsedBytes,激活条件 bufferNotEmpty。使用mutex来保护对numUsedBytes的访问。QWaitCondition::wait() 接收一个mutex作为参数,mutex被调用线程初始化为锁定状态。在线程进入休眠状态之前,mutex会被解锁。而当线程被唤醒时,mutex会处于锁定状态,从锁定状态到等待状态的转换是原子操作。当程序开始运行时,只有生产者可以工作,消费者被阻塞等待bufferNotEmpty条件,一旦生产者在缓冲中放入一个字节,bufferNotEmpty条件被激发,消费者线程于是被唤醒。

#include <QtCore/QCoreApplication>
#include <QSemaphore>
#include <QThread>
#include <cstdlib>
#include <cstdio>
#include <QWaitCondition>
#include <QMutex>
#include <QTime>
const int DataSize = 32;
const int BufferSize = 16;
char buffer[BufferSize];
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
int used = 0;
class Producor:public QThread
{
public:
    void run();
};
void Producor::run()
{
    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    for(int i = 0; i < DataSize; i++)
    {
        mutex.lock();
        if(used == BufferSize)
            bufferNotFull.wait(&mutex);
        mutex.unlock();
        buffer[i%BufferSize] = used;
        mutex.lock();
        used++;
        bufferNotEmpty.wakeAll();
        mutex.unlock();
    }
}
class Consumer:public QThread
{
public:
    void run();
};
void Consumer::run()
{
    for(int i = 0; i < DataSize; i++)
    {
        mutex.lock();
        if(used == 0)
            bufferNotEmpty.wait(&mutex);
        mutex.unlock();
        fprintf(stderr, "%d\n", buffer[i%BufferSize]);
        mutex.lock();
        used--;
        bufferNotFull.wakeAll();
        mutex.unlock();
    }
    fprintf(stderr, "%c", "\n");
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Producor productor;
    Consumer consumer;
    productor.start();
    consumer.start();
    productor.wait();
    consumer.wait();
    return a.exec();
}

四、可重入与线程安全

可重入reentrant与线程安全thread-safe被用来说明一个函数如何用于多线程程序。假如一个类的任何函数在此类的多个不同的实例上,可以被多个线程同时调用,那么这个类被称为是可重入的。假如不同的线程作用在同一个实例上仍可以正常工作,那么称之为“线程安全”的。

1、可重入

大多数c++类天生就是可重入的,因为它们典型地仅仅引用成员数据。任何线程可以在类的一个实例上调用这样的成员函数,只要没有别的线程在同一个实例上调用这个成员函数。

class Counter
{
  public:
      Counter() {n=0;}
      void increment() {++n;}
      void decrement() {--n;}
      int value() const {return n;}
 private:
      int n;
};

Counter类是可重入的,但却不是线程安全的。假如多个线程都试图修改数据成员n,结果未定义。

大多数Qt类是可重入,非线程安全的。有一些类与函数是线程安全的,它们主要是线程相关的类,如QMutex,QCoreApplication::postEvent()。

2、线程安全

所有的GUI类(比如,QWidget和它的子类),操作系统核心类(比如,QProcess)和网络类都不是线程安全的。

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;
 };

Counter类是可重入和线程安全的。QMutexLocker类在构造函数中自动对mutex进行加锁,在析构函数中进行解锁。mutex使用了mutable关键字来修饰,因为在value()函数中对mutex进行加锁与解锁操作,而value()是一个const函数。

QObject与线程

QThread 继承自QObject,它发射信号以指示线程执行开始与结束,而且也提供了许多slots。更有趣的是,QObjects可以用于多线程,这是因为每个线程被允许有它自己的事件循环。
QObject 可重入性

QObject是可重入的。它的大多数非GUI子类,像 QTimer,QTcpSocket,QUdpSocket,

QHttp,QFtp,QProcess也是可重入的,在多个线程中同时使用这些类是可能 的。需要注意的是,这些类被设计成在一个单线程中创建与使用,因此,在一个线程中创建一个对象,而在另外的线程中调用它的函数,这样的行为不能保证工作良好。有三种约束需要注意:

1,QObject的孩子总是应该在它父亲被创建的那个线程中创建。这意味着,你绝不应该传递QThread对象作为另一个对象的父亲(因为QThread对象本身会在另一个线程中被创建)

2,事件驱动对象仅仅在单线程中使用。明确地说,这个规则适用于"定时器机制“与”网格模块“,举例来讲,你不应该在一个线程中开始一个定时器或是连接一个套接字,当这个线程不是这些对象所在的线程。

3,你必须保证在线程中创建的所有对象在你删除QThread前被删除。这很容易做到:你可以run()函数运行的栈上创建对象。

尽管QObject是可重入的,但GUI类,特别是QWidget与它的所有子类都是不可重入的。 它们仅用于主线程。正如前面提到过的,QCoreApplication::exec()也必须从那个线程中被调用。实践上,不会在别的线程中使用GUI 类,它们工作在主线程上,把一些耗时的操作放入独立的工作线程中,当工作线程运行完成,把结果在主线程所拥有的屏幕上显示。

五、线程与信号和槽

run 是线程的入口,run的开始和结束意味着线程的开始和结束,run函数中的代码在新建线程中执行。

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

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection )

Qt::AutoConnection类型:Qt支持6种连接方式

A、Qt::DirectConnection(直连方式)(信号与槽函数关系类似于函数调用,同步执行)

当信号发出后,相应的槽函数将立即被调用。emit语句后的代码将在所有槽函数执行完毕后被执行。

当信号发射时,槽函数将直接被调用。

无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。

B、Qt::QueuedConnection(队列方式)(此时信号被塞到信号队列里了,信号与槽函数关系类似于消息通信,异步执行)

当信号发出后,排队到信号队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,调用相应的槽函数。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕。

当控制权回到接受者所依附线程的事件循环时,槽函数被调用。

槽函数在接收者所依附线程执行。

C、Qt::AutoConnection(自动方式)

Qt的默认连接方式,如果信号的发出和接收这个信号的对象同属一个线程,那个工作方式与直连方式相同;否则工作方式与排队方式相同。

如果信号在接收者所依附的线程内发射,则等同于直接连接

如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接

D、Qt::BlockingQueuedConnection(信号和槽必须在不同的线程中,否则就产生死锁)

这个是完全同步队列只有槽线程执行完成才会返回,否则发送线程也会一直等待,相当于是不同的线程可以同步起来执行。

E、Qt::UniqueConnection

与默认工作方式相同,只是不能重复连接相同的信号和槽,因为如果重复连接就会导致一个信号发出,对应槽函数就会执行多次。

F、Qt::AutoCompatConnection

是为了连接Qt4与Qt3的信号槽机制兼容方式,工作方式与Qt::AutoConnection一样。

如果这个参数不设置的话,默认表示的是那种方式呢?

没加的话与直连方式相同:当信号发出后,相应的槽函数将立即被调用。emit语句后的代码将在所有槽函数执行完毕后被执行。在这个线程内是顺序执行、同步的,但是与其它线程之间肯定是异步的了。如果使用多线程,仍然需要手动同步。

slot 函数属于我们在main中创建的对象 thread,即thread依附于主线程

队列连接告诉我们:槽函数在接受者所依附线程执行。即 slot 将在主线程执行

直接连接告诉我们:槽函数在发送信号的线程执行。

自动连接告诉我们:二者不同,等同于队列连接。即 slot 在主线程执行

QThread是用来管理线程的,QThread对象所依附的线程和所管理的线程并不是同一个概念。QThread所依附的线程,就是创建QThread对象的线程,QThread 所管理的线程,就是run启动的线程,也就是新建线程。QThread对象依附在主线程中,QThread对象的slot函数会在主线程中执行,而不是次线程。除非:

QThread对象依附到次线程中(通过movetoThread)

slot 和信号是直接连接,且信号在次线程中发射

时间: 2024-10-05 23:46:39

QT开发(三十四)——QT多线程编程的相关文章

QT开发(十四)——QT绘图系统

QT开发(十四)--QT绘图系统 一.QT绘图原理 Qt4中的2D绘图系统称为Arthur绘图系统,可以使用相同的API在屏幕上和绘图设备上进行绘制,主要基于QPainter.QPainterDevice和 QPainterEngine.QPainter执行绘图操作,QPainterDevice提供绘图设备,是一个二维空间的抽象,QPainterEngine提供一些接口.QPainter用来执行具体的绘图相关操作,如画点,画线,填充,变换,alpha通道等.QPaintDevice类是能够进行绘

Python进阶(三十四)-Python3多线程解读

Python进阶(三十四)-Python3多线程解读 线程讲解 ??多线程类似于同时执行多个不同程序,多线程运行有如下优点: 使用线程可以把占据长时间的程序中的任务放到后台去处理. 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度. 程序的运行速度可能加快. 在一些等待的任务实现上如用户输入.文件读写和网络收发数据等,线程就比较有用了.在这种情况下我们可以释放一些珍贵的资源如内存占用等等. ??线程在执行过程中与进程还是有区别的.每个独立

java 面向对象编程--第十四章 多线程编程

1.  多任务处理有两种类型:基于进程和基于线程. 2.  进程是指一种“自包容”的运行程序,由操作系统直接管理,直接运行,有自己的地址空间,每个进程一开启都会消耗内存. 3.  线程是进程内部单一的顺序控制流.一个进程拥有多个线程.多个线程共享一个进程的内存空间. 4.  基于进程的特点是允许计算机同时运行两个或更多的程序. 5.  基于线程的多任务处理环境中,线程是最小的处理单位. 6.  基于进程所需的开销更少:每个进程都需要操作系统为其分配独立的内存空间:同意进程中的所有线程都在同意内存

QT开发(十六)——QT绘图实例-钟表

QT开发(十六)--QT绘图实例-钟表 一.钟表实现原理 钟表的实现需要设置定时器,定时器每隔一秒发送timeout()信号到QWidget::update()槽函数,update()槽函数将会重绘一次窗口,重写重绘事件函数paintEvent(QPaintEvent *event),根据获取的当前系统时间的时钟.分钟.秒钟重绘钟表的时针.分针.秒针. QTimer *timer = new QTimer(this); timer->start(1000);//一秒钟 connect(timer

QT开发(十九)——QT内存泄漏问题

QT开发(十九)--QT内存泄漏问题 一.QT对象间的父子关系 QT最基础和核心的类是:QObject,QObject内部有一个list,会保存children,还有一个指针保存parent,当自己析构时,会自己从parent列表中删除并且析构所有的children. QT对象之间可以存在父子关系,每一个对象都可以保存它所有子对象的指针,每一个对象都有一个指向其父对象的指针. 当指定QT对象的父对象时,父对象会在子对象链表中加入该对象的指针,该对象会保存指向其父对象的指针. 当QT对象被销毁时,

QT开发(十二)——QT事件处理机制

QT开发(十二)--QT事件处理机制 一.QT事件简介 QT程序是事件驱动的, 程序的每个动作都是由内部某个事件所触发.QT事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. 常见的QT事件类型如下: 键盘事件: 按键按下和松开 鼠标事件: 鼠标移动,鼠标按键的按下和松开 拖放事件: 用鼠标进行拖放 滚轮事件: 鼠标滚轮滚动 绘屏事件: 重绘屏幕的某些部分 定时事件: 定时器到时 焦点事件: 键盘焦点移动 进入和离开事件: 鼠标移入widget之内,或是移出 移动事件: widget的

QT开发(十五)——QT坐标系统

QT开发(十五)--QT坐标系统 一.QT坐标系简介 Qt中每一个窗口都有一个坐标系,默认窗口左上角为坐标原点,然后水平向右依次增大,水平向左依次减小,垂直向下依次增大,垂直向上依次减小.原点即为(0,0)点,以像素为单位增减. 二.坐标系变换 坐标系变换是利用变换矩阵来进行的, 通常利用QTransform类来设置变换矩阵.QPainter类提供了对坐标系的平移,缩放,旋转,扭曲等变换函数. void translate(const QPointF & offset) void transla

[原创]ActionScript3游戏中的图像编程(连载三十四)

2.2.7 关于Photoshop的图层挖空投影 在Photoshop里面,不管图层挖空投影的复选框是否处于勾选状态,显示出来的效果都几乎没有任何差别.那这个挖空的作用何在?不急,我们看看Flash里的挖空选项. Flash里的挖空很明显,图 2.23展示了挖空后的效果. 图 2.23 Flash的挖空投影 Flash的投影滤镜把常规显示的像素颜色都掏空了.从挖空的现象和隐藏对象的字面意思来看,两者含义似乎一致,但结果却出乎我的意料.(图 2.24) 图 2.24 隐藏对象 可见,投影与文字本身

ActionScript3游戏中的图像编程(连载三十四)

2.2.7 关于Photoshop的图层挖空投影 在Photoshop里面,不管图层挖空投影的复选框是否处于勾选状态,显示出来的效果都几乎没有任何差别.那这个挖空的作用何在?不急,我们看看Flash里的挖空选项. Flash里的挖空很明显,图 2.23展示了挖空后的效果. 图 2.23 Flash的挖空投影 Flash的投影滤镜把常规显示的像素颜色都掏空了.从挖空的现象和隐藏对象的字面意思来看,两者含义似乎一致,但结果却出乎我的意料.(图 2.24) 图 2.24 隐藏对象 可见,投影与文字本身