同步线程
创始人
2024-05-25 02:26:16
0

  ↵

由于这节内容资料比较少,所以以下内容总结自Qt官方文献,在文章最后会给出相应链接。 

线程的目的是允许并行运行,但有时线程必须停止等待其他线程。例如,如果两个线程尝试访问同一个变量,这样的话结果是未定义的。强制线程相互等待的原则成为互斥,是一种保护共享资源的常用技术。

同步线程类:

  • QMutex  互斥锁
  • QReadWriteLock  读-写锁
  • QSemaphore  信号量
  • QWaitCondition 条件变量

QMutex(互斥锁)

提供一个互斥锁,在任何事件至多有一个线程可以获得mutex。如果一个线程尝试获得mutex,而mutex已经锁住,那么这个线程将会睡眠

QReadWriteLock  (读-写锁)

读-写锁,于QMutex相似,但它对共享数据进行访问的区分,分为“读”,“写”访问,允许多个线程同时对数据进行“读”访问,QReadWiteLock的并行度大于QMutex

QSemaphore  信号量

QSemaphore是QMutex的概括,可保护一定数量的相同资源。相比之下,QMutex 只保护一种资源。信号量示例显示了信号量的典型应用:同步对生产者和使用者之间循环缓冲区的访问

QWaitCondition 条件变量

QWaitCondition,允许一个线程在一些条件满足时唤醒其他线程, 不是通过强制执行互斥而是通过提供条件变量来同步线程。一个或多个线程可以被堵塞来等待一个QWaitCondition,使用wakeOne()可以唤醒一个随机选取的等待的线程,使用wakeAll()可以唤醒所有正在等待的线程

QMutex

QMutex通常和QMutexLocker一起使用,才可以确保这可以轻松确保锁定和解锁的执行一致。

常用函数:

isRecursive()判断互斥锁是否为递归(Qt 5.7引入)
lock()锁定互斥锁,如果另一个线程锁定了互斥锁,则此调用将阻塞,直到该线程解锁它
tryLock(int )尝试锁定互斥锁,如果已获得锁,则函数返回true 否则返回 false,可以设置等待时间。如果获得锁则必须使用unlock()解锁互斥锁,这样才能在另一个线程才能成功锁定它。
try_lock()尝试锁定互斥锁,如果已获得锁,则函数返回true 否则返回 false,提供此功能是为了与标准库概念兼容,等价于tryLock()(Qt  5.8 引入)
try_lock_for()

尝试锁定互斥锁,如果已获得锁,则函数返回true 否则返回 false,

如果获得锁,必须使用unlock()解锁互斥锁

如果为递归互斥锁,允许在同一线程的容易个互斥锁上多次调用该函数

如果此互斥锁是非递归互斥锁,则在尝试递归锁定互斥锁时,此函数将始终返回 false。(Qt  5.8 引入)

try_lock_until()

尝试锁定互斥锁,如果已获得锁,则函数返回true 否则返回 false,

如果获得锁,必须使用unlock()解锁互斥锁

如果为递归互斥锁,允许在同一线程的容易个互斥锁上多次调用该函数

如果此互斥锁是非递归互斥锁,则在尝试递归锁定互斥锁时,此函数将始终返回 false。(Qt  5.8 引入)

unlock()解锁互斥锁。

 QMutex::QMutex(QMutex::RecursionMode mode)

创建一个互斥锁时可以设置模式

QMutex::Recursive在这种模式下,线程可以多次锁定同一个互斥锁,并且在进行相应数量的 unlock() 调用之前,互斥锁不会被解锁
QMutex::NonRecursive在此模式下,线程只能锁定一次互斥锁

互斥锁使用场景:当一个变量同时被多个线程访问

int number=10;void text()
{number*=2;number+=5;
}
void text1()
{number+=10;number*3;
}

正常调用 text()和text1()的话

text()  number=2*10=20+5=25
text1() number=25+10=30*3=90

同时调用的话可能会发生以下情况

线程1调用text()
number=10*2=20;
线程2调用text1(),现在text()的调用暂停
number=20+10=30
number=30*3=90;
继续完成线程text()
number=90+5=95

为了防止以上情况,可以上个互斥锁,使得调用完某个函数才能调用其他函数。

QMutex mutex;//互斥锁int number=10;void text()
{mutex.lock();//上锁number*=2;number+=5;mutex.unlock();//解锁  
}void text1()
{mutex.lock();//上锁number+=10;number*=3;mutex.unlock();//解锁  
}

QMutexLocker类

QMutexLocker类是一个方便点的类,可以简化锁定和解锁的互斥锁。

使用方法:QMutexLocker应该在需要锁定QMutex的函数中创建,创建一个QMutexLock时,互斥锁被锁定。(更加方便)

函数:

mutex(()返回正在运行的互斥锁
relock()重新锁定未锁定的互斥锁锁。
unlock()解锁此互斥锁

 使用QMutex的情况:需要在分支中解锁互斥锁

int complexFunction(int flag)
{mutex.lock();int retVal = 0;switch (flag) {case 0:case 1:retVal = moreComplexFunction(flag);break;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;
}

QMutexLocker的使用

int complexFunction(int flag)
{QMutexLocker locker(&mutex);//创建一个QmutexLocker对象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;
}

当函数执行完后,QMutexLocker对象销毁时,互斥锁会解锁,就不用每个分支去解锁。

使用QMutexLocker::mutex()可以获取当前正在运行的互斥锁。 

 QReadWriteLock(读-写锁)

读写锁是一种同步工具,用于保护可以访问以进行读取和写入的资源。如果要允许多个线程同时具有只读访问权限,则这种类型的锁定很有用,但是一旦一个线程想要写入资源,就必须阻止所有其他线程,直到写入完成。

函数:

lockForRead()锁定读取,

如果另一个线程已锁定写入,此函数将阻止当前线程。

如果线程已锁定写入,则无法锁定读取。

lockForWrite()

锁定写入

如果另一个线程(包括当前线程)已锁定以进行读取或写入,则此函数将阻止当前线程

tryLockForRead()尝试锁定以进行读取,如果线程已锁定写入,则无法锁定读取
tryLockForWrite()尝试锁定以进行写入,如果线程已锁定以进行读取,则无法锁定写入
unlock()解锁

QReadWriteLock通常使用于经常读取数据,可以多个线程同时读取数据。

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

 QSemaphore(信号量)

 信号量是互斥锁的概括。虽然互斥锁只能锁定一次,但可以多次获取信号量。信号量通常用于保护一定数量的相同资源

函数:

acquire(int n)尝试获取n个由信号量保护的资源,当资源不够时将堵塞直到资源足够。
release(int n)

释放由信号量保护的 n 个资源,

此函数也可用于“创建”资源。

available()返回信号灯当前可用的资源数。这个数字永远不能是负数。
tryAcquire(int n)尝试获取由信号量保护的资源,成功返回true,否则返回false

tryAcquire(int n,int timeout

尝试获取由信号量保护的资源,成功返回true,否则返回false,调用最多等待timeout秒

 QSemaphore的创建

QSemaphore::QSemaphore(int n=0)

 创建新的信号量,并将其保护的资源数初始化为 n(默认为 0)。

    QSemaphore sem(5);sem.acquire(3);qDebug()<<"资源还有"<

当释放的资源多余需要释放的资源时,多余的会进行创建

    QSemaphore sem(5);sem.acquire(3);qDebug()<<"资源还有"<

 

当资源少于需要获取的资源时,不会获取成功。

    QSemaphore sem(5);if(sem.tryAcquire(7)){qDebug()<<"获取成功";}else{qDebug()<<"获取失败";}

 

 

 QWaitCondition

允许一个线程在一些条件满足时唤醒其他线程, 不是通过强制执行互斥而是通过提供条件变量来同步线程。一个或多个线程可以被堵塞来等待一个QWaitCondition,使用wakeOne()可以唤醒一个随机选取的等待的线程,使用wakeAll()可以唤醒所有正在等待的线程

函数:

wait(QMutex*,time)释放锁定的互斥锁并等待等待条件
wakeOne()唤醒一个等待等待条件的线程。唤醒的线程取决于操作系统的调度策略,无法控制或预测。如果要唤醒特定线程,解决方案通常是使用不同的等待条件,并让不同的线程等待不同的条件
wakeAll()唤醒等待等待条件的所有线程。线程的唤醒顺序取决于操作系统的调度策略,无法控制或预测。
notify_one()

提供此函数是为了与 STL 兼容。它等效于 wakeOne()Qt 5.8中引入

notify_all()提供此函数是为了与 STL 兼容。它等效于 wakeAll()Qt 5.8中引入

 QWaitCondition的示例:

使用QWaitCondition的QMutex解决生产者-消费者问题

设置全局变量: 

const int DataSize = 100000;//生产者将生成的数据量const int BufferSize = 8192;//缓冲区
char buffer[BufferSize];//两个等待条件,一个互斥锁和计数器
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
int numUsedBytes = 0;

生产者类:

class Producer : public QThread
{
public:Producer(QObject *parent = NULL) : QThread(parent){}void run() override{for (int i = 0; i < DataSize; ++i) {mutex.lock();//上锁if (numUsedBytes == BufferSize)//检查缓冲区是否已满bufferNotFull.wait(&mutex);//已满的话等待条件满足mutex.unlock();//解锁//存放数据(随机数)buffer[i % BufferSize] = "ACGT"[QRandomGenerator::global()->bounded(4)];mutex.lock();//上锁++numUsedBytes;计数器+1bufferNotEmpty.wakeAll();//唤醒全部线程mutex.unlock();//解锁}}
};

消费者类:

class Consumer : public QThread
{Q_OBJECT
public:Consumer(QObject *parent = NULL) : QThread(parent){}void run() override{for (int i = 0; i < DataSize; ++i) {mutex.lock();if (numUsedBytes == 0)//检查缓冲区是否为空bufferNotEmpty.wait(&mutex);mutex.unlock();fprintf(stderr, "%c", buffer[i % BufferSize]);//输出内容mutex.lock();--numUsedBytes;//计数器-1bufferNotFull.wakeAll();mutex.unlock();}fprintf(stderr, "\n");}signals:void stringConsumed(const QString &text);
};

main函数:

int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);Producer producer;//生产者Consumer consumer;//消费者producer.start();//开启线程consumer.start();//开启线程//两个线程调用wait(),阻塞线程,确保两个线程在退出前都有时间能完成main()producer.wait();consumer.wait();return 0;
}

QSemaphore信号量的生产者消费者问题。

全局变量 :

const int DataSize=1000;//生产者的数据量
const int BufferSize=800;//缓冲区大小
char buffer[Buffersize];
QSemaphore freeBytes(BufferSize);//控制缓冲区的信号量
QSemaphore usedBytes;//控制已经使用的缓冲区

 生产者类:

class Producer :public QThread
{
public:void run();
}void Producer::run()
{qsrand(QTime(0.0.0).secsTo(QTime::currentTime()));//随机数for(int i=0;i

消费者类:

class Consumer :public QThread
{
public:void run();
}void Producer::run()
{for(int i=0;i

 main()

int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);Producer producer;//生产者Consumer consumer;//消费者producer.start();//开启线程consumer.start();//开启线程//两个线程调用wait(),阻塞线程,确保两个线程在退出前都有时间能完成main()producer.wait();consumer.wait();return 0;
}

参考文献:

同步线程|Qt 5.15

QMutex 类 |Qt核心 5.15.12

QMutexLocker Class |Qt核心 5.15.12

QReadWriteLock 类 |Qt核心 5.15.12

QSemaphore Class | Qt Core 5.15.12

QWaitCondition Class | Qt Core 5.15.12 

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...