目录
描述
使用
线程不安全
线程安全
释放锁问题
其他的锁
条件变量和信号量
描述
多线程程序太复杂了
在C/C++ 和 Linux中,我们为了保证线程安全,简单的方式就是加锁
为此 Qt 也封装了自己的一套锁管理
使用
线程不安全
我们先测验一下线程不安全的情况,先自定义一个类,等会要重写 QThread 类
thread.h
thread.cpp
一个简单的累加
运行发现,结果并不是我们所想象中的那样
线程安全
对++ 进行加锁,在CPU指令之中,++操作一共有三个指令,本身不是原子的,所以有线程不安全的问题,我们对涉及到修改的代码块进行加锁,保证线程安全
创建一共静态的锁对象,确保是使用同一把锁
运行正常,累加到了应有的数值
释放锁问题
关于锁的释放,是有可能忘记释放的,忘记 unlock
在临界区之中,由于可能存在 判断,异常之类的操作,可能会导致锁没有释放
在C++释放内存中也有这样的问题存在,因此C++ 引入了智能指针,来解决内存释放和锁释放的问题
在出花括号区域的时候,lock_guard会自动调用析构,来释放 锁
Qt 为了锁的释放也参考了这种做法,创建了 mutexLocker
使用如下,和C++之中的 std::lock_guard 使用方式一样
不建议和C++混着用锁相关函数
其他的锁
Qt中还有很多其他的锁,诸如读写锁,在特定的场景有着很好的发挥
QReadWriteLocker、QReadLocker、QWriteLocker
QReadWriteLock 是读写锁类,⽤于控制读和写的并发访问。
QReadLocker ⽤于读操作上锁,允许多个线程同时读取共享资源。
QWriteLocker ⽤于写操作上锁,只允许⼀个线程写⼊共享资源。
条件变量和信号量
条件变量
在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才能执⾏,这时就会出现问题。这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。当条件满⾜时,等待条件的线程将被另⼀个线程唤醒。
在 Qt 中,专⻔提供了 QWaitCondition类 来解决像上述这样的问题。
特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。
⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调
伪代码如下,使用过程也大致如下
QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())
{
condition.wait(&mutex); //等待条件满⾜并释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();
信号量
有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运⾏程序的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量。
特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。
⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题
伪代码,使用过程如下
QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作