【OpenCV与Qt5】多线程【3线程同步工具】

Mutexes

一般情况下,如果两个线程同时访问同一对象(例如变量或者类实例),而且线程对对象的访问顺序非常重要,那么最终的结果不是我们想要的。
假如有一个线程在一直读取名为image的Mat对象(假设在重写的run函数中或者使用moveToThread方法的独立的线程中)

forever
{
    image = imread("image.jpg");
}

forever宏是Qt创建无限循环的宏,等同于for(;;),使用这样的宏可以增加我们程序的可读性。
第二个线程一直在修改图像

forever
{
    cvtColor(image, image, CV_BGR2GRAY);
    resize(image, image, Size(), 0.5, 0.5);
}

假如这两个线程同时运行,在某个时间点,第一个线程可能在第二个线程的cvtColor之后,resize之前被调用,那么我们不会得到一个大小等于原图一半的灰度图,我们没有任何方法阻止这个事情发生,因为它们完全由操作系统在运行时处理。在多线程问题中,有一类竟态条件问题,解决方法是保证每个线程在轮到它执行的时候才去访问对象。这种解决方法叫做访问序列化(access serialization),在多线程编程中,通常靠互斥来解决。
互斥是防止多个线程同时访问同一个对象的方法。Qt中提供QMutex来处理访问序列化问题。只需创建一个QMutex对象,把它命名为imageMutex,然后我们在线程访问Mat对象时锁住互斥,当访问结束时解锁互斥即可。

forever
{
    imageMutex.lock();
    image = imread("image.jpg");
    imageMutex.unlock();
}

对于第二个线程,我们有

forever
{
    imageMutex.lock();
    cvtColor(image, image, CV_BGR2GRAY);
    resize(image, image, Size(), 0.5, 0.5);
    imageMutex.unlock():
}

通过这种方法,当任何一个线程想要访问image时,首先要锁住mutex,假如线程执行到一半了,操作系统决定要切换到另一个新线程,另一个线程同样想要锁住mutex,但是此时mutex已经被锁了,在老线程解锁之前,新线程会一直阻塞,可以把这个过程想像为试图去拿一把锁的钥匙。只有锁住mutex的线程才可以解锁Mutex,这样就保证了当一个线程在访问一个对象时,另一个想访问这个对象的线程只能等待前一个线程访问完毕。
当对同一对象访问的线程增加时,使用mutex会成为一个负担。我们在Qt中可以使用QMutexLocker类来处理解锁、锁住mutex,我们把上面的代码改写为:

forever
{
    QMutexLocker locker(&imageMutex);
    image = imread("image.jpg");
}

第二个线程中

forever
{
    QMutexLocker locker(&imageMutex);
    cvtColor(image, image, CV_BGR2GRAY);
    resize(image, image, Size(), 0.5, 0.5);
}

当构造QMutexLocker时传入mutex,这个锁被锁住,当QMutexLocker销毁时(变量超出作用域),锁被解锁。

读写锁

尽管mutex很强大,但它没有一些特定的功能,例如设定不同类型的锁。所以即使它在访问序列化的时候很有用,但不能有效的处理读写序列化问题。
假如我们想让多个线程同时读同一对象(例如变量,实例,文件等),同一时刻,只能让一个线程修改这个对象,这种情况下,我们要用于mutex的增加版-读-写锁机制。Qt提供了QReadWriteLock类:

  • 如果一个线程调用 lockForRead 函数,其它线程仍然可以调用 lockForRead 函数来敏感对象。
  • 如果一个线程调用 lockForRead 函数, 其它线程只能等到解锁时才可调用lockForWrite
  • 如果某一线程调用 lockForWrite 函数, 其它线程都不可以读或写敏感对象
  • 如果在调用 lockForWrite 之前,已经有线程调用lockForRead了,所有调用lockForRead锁的线程都必须等待这个有写入锁的线程,也就是说,lockForWrite拥有更高的权限。

简单来说,这一机制保证了:

  • 多个线程可以同时访问某一对象,写入者必须等待读取者完成才可以执行写入。
  • 只允许一个写入者写入对象
  • 在读者过多时,为确保写入者不永远等待,写入者拥有更高权限

下面例子来了,注意下面的 lock 变量是 QReadWriteLock 类,read_image是一个任意的只作访问目的的函数

forever
{
    lock.lockForRead();
    read_image();
    lock.unlock():
}

类似的,在需要写入的线程里,我们有 write_image 用来代表任意需执行写入的函数

forever
{
    lock.lockForWrite();
    write_image();
    lock.unlock();
}   

与QMutex相似,我们用QMutexLocker来简化使用lock unlock函数,这里有QReadLocker 和QWriteLocker 类用于锁定或解锁QReadWriteLock,我们将之前代码改写为

forever
{
    QReadLocker locker(&lock);
    Read_image();
}

forever
{
    QWriteLocker locker(&lock);
    write_image();
}

Semaphores - 计数量

有些时候,我们要保证多个线程能访问有限数目的相同资源,例如,一个设备只有有限的内存,我们希望需要扩展内存的线程要兼顾可用内存问题,常常用semaphore来处理这一问题。semaphore不仅能锁定、解锁,还能追踪可用资源。
Qt提供了QSemaphore,以下是semaphore的一些函数

  • acquire: 此函数用于获取特定大小的内资源,假如没有足够大的资源,那么线程将被阻塞,直到有充足资源。
  • release: 此函数用于释放线程中使用的并且不再需要的资源
  • available: 此·函数用于获取可用资源的总量。这个函数让线程在资源不足时,不去等待资源,而是执行其它任务

假如有100MB的内存,每个线程所需的内存都不一样,例如所需的内存是基于要处理的图像的大小。我们可以借助QSemaphore确保所有的线程只能用可用的内存大小,不能超过这个总量。

QSemaphore memSem(100);

在每个线程里面,在内存消耗很大的过程的前后,我们需要申请、释放所需的内存,像这样

memSem.acquire(X);
process_image(); // memory intensive process
memSem.release(X);

注意在这个示例里,假如X大于100,则必须等待release的内存大于或等于acquire的内存,才会执行内存集中的运算。这意味着可用资源的总量可以通过release函数来增加。

等待条件

有时我们希望一个线程在释放了读写锁之后进入休眠,这样其他线程就可以动作了,当某一条件达到时,它可以由其它线程唤醒。
假如有一些线程处理Mat对象,一个线程负责读取图像。假如有个进程负责创建这个图像文件,由于图像被多个线程占用,我们要用mutex保证每次只能有一个线程访问。然而,当图像不存在的时候,读线程需要等待,所以,我们引入:

forever
{   
    mutex.lock();
    imageExistsCond.wait(&mutex);
    read_image();
    mutex_unlock();
}

注意mutex是QMutex类,imageExistsCond是QWaitCondition类,这个代码段表示锁定,开始读图像,但是假如你需要等待图像存在这个条件的话,你先释放掉这个锁,这样其它线程就可以运行了。同时需要其它线程唤醒这个线程,比如

forever
{
    if(QFile::exists("image.jpg")
        imageExistsCond.wakeAll();
}

此线程只是简单的检查图像是否存在,假如条件通过,它就唤醒其它线程。我们可以用wakeOne来唤醒等待这一条件的单一线程。

到此,我们的线程同步工具介绍完了,这一部分是介绍的类是Qt中有关线程同步最重要的类。请参考Qt文档学习更多的函数用来提高你的程序。在使用low-level线程的时候,要使用这些工具进行线程间通讯。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值