多线程(2)

二、多线程的控制

实现线程的互斥与同步常使用的类有QMutex、QMutexLocker、QReadWriteLock、QReadLocker、 QWriteLocker、QSemaphore和QWaitCondition。

下面举一个例子加以说明:

class Key
{
public:
Key() {key=0;}
int creatKey() {++key; return key;}
int value()const {return key;}
private:
int key;
};

在多线程环境下,这个类是不安全的,因为存在多个线程同时修改私有成员key,其结果是不可预知的。

相同两个程序同时使用一个线程会导致预期之外的结果。(但加上线程锁后也可达到预期值)

多个线程中操作同一个对象导致的前后不一致。

数据库,多个操作同时成功或失败。数据事务,底层使用锁机制

想要改变线程的顺序,就要改变线程的优先级;

虽然Key类产生主键的函数creatKey()只有一条语句执行修改成员变量key的值,但是C++的“++”操作符并 不是原子操作,通常编译后,它将被展开成为以下三条机器命令:

  • 将变量值载入寄存器。
  • 将寄存器中的值加1。
  • 将寄存器中的值写回主存(内存)。

互斥量:

QMutex类:

(线程锁,作用:将一段代码作为一个整体执行。)lock() 上锁 unlock() 解锁

两个线程同时进入,线程1先上锁,线程2等待,线程1结束之后释放锁;线程2上锁,线程2结束之后释放锁;

没有unlock(),也就是没有解锁;那就是死锁,永远没有释放。(有一个解决方法QMutexLocker上锁器,负责线程锁的上锁操作和解锁操作[QMutexLocker locker(&mutex)])

QMutex类的lock()函数用于锁住互斥量。如果互斥量处于解锁状态,则当前线程就会立即抓住并锁定 它,否则当前线程就会被阻塞,直到持有这个互斥量的线程对它解锁。线程调用lock()函数后就会持有这 个互斥量,直到调用unlock()操作为止。

QMutex类还提供了一个tryLock()函数。如果互斥量已被锁定,则立即返回。

例如:

class Key
{
public:
Key() {key=0;}
int creatKey() { mutex.lock(); ++key; return key; mutex. unlock();}
int value()const { mutex.lock(); return key; mutex.unlock();}
private:
int key;
QMutex mutex;
};

QMutexLocker类

Qt提供的QMutexLocker类可以简化互斥量的处理,它在构造函数中接收一个QMutex对象作为参数并 将其锁定,在析构函数中解锁这个互斥量,这样就解决了以上问题。

举个例子:

class Key
{
public:
Key() {key=0;}
int creatKey() { QMutexLocker locker(&mutex); ++key; return key; }
int value()const { QMutexLocker locker(&mutex); return key; }
private:
int key;
QMutex mutex;
};

locker对象作为局部变量会在函数退出时结束其作用域,从而自动对互斥量mutex解锁。

信号量:

信号量可以理解为对互斥量功能的扩展,互斥量只能锁定一次而信号量可以获取多次,它可以用来保护 一定数量的同种资源。信号量的典型用例是控制生产者/消费者之间共享的环形缓冲区。 生产者/消费者实例中对同步的需求有两处: (1)如果生产者过快地生产数据,将会覆盖消费者还没有读取的数据。

(2)如果消费者过快地读取数据,将越过生产者并且读取到一些过期数据。

针对以上问题,有两种解决方法

(1)首先使生产者填满整个缓冲区,然后等待消费者读取整个缓冲区,这是一种比较笨拙的方法。

(2)使生产者和消费者线程同时分别操作缓冲区的不同部分,这是一种比较高效的方法。

QSemaphore类

接口解析 : (一个生产者 三个消费者 四个线程 数组资源)

QSemaphore类代表一个信号量类。以下是它的一些方法:

acquire 尝试获取一个资源(消费者操作)

release 去释放资源(生产者)

//构造方法,参数n代表初始允许的资源数量
QSemaphore::QSemaphore(int n = 0)

//返回当前信号量允许的资源数量
int QSemaphore::available() const

//尝试去请求资源,默认为一个资源。如果n大于available()值,则该方法阻塞,直到有足够的资源。
void QSemaphore::acquire(int n = 1)

//释放n个资源,默认为一个资源。
void QSemaphore::release(int n = 1)

//尝试去请求资源,默认为一个资源。如果成功返回true,如果n大于available()值,则该方法阻塞
timeout 毫秒,

//若timeout 毫秒内有足够的资源则返回true,否则退出阻塞状态,返回false。
bool QSemaphore::tryAcquire(int n, int timeout)

//尝试去请求资源,默认为一个资源。如果成功返回true,如果失败立即返回false。
bool QSemaphore::tryAcquire(int n = 1)

线程等待与唤醒

使用QWaitCondition类解决生产者和消费者问题。

接口解析:

QWaitCondition类提供了用于同步线程的条件变量。比如一个或多个线程可以通过QWaitCondition类 对象进入阻塞状态直到在另一个线程中通过调用该QWaitCondition类对象的wakeOne() 或者 wakeAll() 方法去唤醒某一线程或者全部线程。以达到线程控制的效果。

//释放lockedMutex,并进入等待状态。调用前lockedMutex必须处于lock状态。time 为等待的最长时
间,默认无限,单位毫秒。
bool wait(QMutex *lockedMutex, unsigned long time = ULONG_MAX)
//同上,参数为QReadWriteLock
bool wait(QReadWriteLock *lockedReadWriteLock, unsigned long time = LONG_MAX)
//唤醒所有等待中的线程
void wakeAll()
//唤醒所有等待中的线程的其中一个,取决于操作系统的线程调度策略。
void wakeOne()

三、线程死锁

死锁 (deadlock): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互 相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁, 这些永远在互相等待的进程(线程)称为死锁进程(线程)。

如果一个线程锁住了一个资源而没有释放它,由于其他的线程亦无法访问该资源而一直等待,导致 应用程序被冻结,比如在一个方法中,在释放资源之前发生异常而导致强制结束。

在多线程环境中使用QMutexLocker, QReadLocker 和 QWriteLocker可以有效地避免死锁的发生, 当三类对象创建时自动上锁,销毁时自动解锁。

比如另一种情况,A等待B释放资源,而B等待A释放另一种资源,此时A和B由于同时等待对方释放 资源而发生死锁。这是由于进程(线程)推进的顺序不恰当而导致的,属于设计上的问题,在多线程编 程时应尽可能避免此类现象发生。

四、线程池

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任 务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程 单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使 所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在 一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他 们要等到其他线程完成后才启动。

QT中提供了QThreadPool类来提供线程池相关的功能。QThreadPool类管理一个线程的集合。 QThreadPool类中的线程需要是一个继承于抽象类QRunable的类对象。

QRunable类的主要接口如下:

// run方法,实现线程功能,需要派生类重写
virtual void run() = 0
//若设置为true,线程池将在执行完run方法后删除该对象,false为不删除。默认为true
//注意必须要在QThreadPool::start()方法之前调用
void setAutoDelete(bool autoDelete)

QThreadPool的主要接口如下:

//静态方法 ,获取全局线程池
QThreadPool *globalInstance()
//成员方法
//返回正在运行的线程数量
int activeThreadCount() const
//移除一个未启动的QRunnable对象,若autoDelete为true,则释放它
void cancel(QRunnable *runnable)
//移除所有未启动的QRunnable对象,若autoDelete为true,则释放它
void clear()
//返回线程池可用最大线程数量
int maxThreadCount() const
//设置线程池可用最大线程数量,最少一个
void setMaxThreadCount(int maxThreadCount)
//启动一个线程,priority为优先级,同QThread的优先级
void start(QRunnable *runnable, int priority = 0)
//尝试去启动一个线程,若失败则返回false
bool tryStart(QRunnable *runnable)
//等待msecs时间,若所有线程结束,则移除所有线程,返回true,若线程未结束则返回false。
bool waitForDone(int msecs = -1)

举例如下:

class HelloworldTask:public QRunnable{
    void run(){
        qDebug() << "Hello world from thread" << QThread::currentThread();
    }
}
HelloWorldTask *hello = new HelloWorldTask();
//线程池启动了线程hello,并在运行结束后调用delete释放内存
QThreadPool::globalInstance()->start(hello);

你可能感兴趣的:(qt)