A semaphore is a generalization of a mutex. While a mutex can only be locked once, it's possible to acquire a semaphore multiple times. Semaphores are typically used to protect a certain number of identical resources.
Semaphores support two fundamental operations, acquire() and release():
- acquire(n) tries to acquire n resources. If n > available(), this call will block until enough resources are available.
- release(n) releases n resources.This function can be used to "create" resources as well.
There's also a tryAcquire() function that returns immediately if it cannot acquire the resources, and an available() function that returns the number of available resources at any time.
QSemaphore也可以被用来使线程的执行顺序化,和QMutex的方法相似。信号量和互斥量的不同在于,信号量可以在同一时间被多于一个的线程访问。在博文中通常将QSemaphore具体使用定义为 生产者/消费者 模式,作者觉得这样子定义可以将的更详细、明白,理解也更容易理解一些。
本文详解QSemaphore 线程同步过程中,有两个栗子在这里给大家举一下,看完本文更深入的了解生产者/消费者。
文章项目结构:
栗子 一、 QSemaphore 线程同步 生产者/消费者
调用实现
初始化全局变量
实现生产者线程
实现消费者线程
栗子 二、 QSemaphore 线程同步 一个生产者多个消费者
调用实现
初始化全局变量
实现生产者线程
实现消费者线程
运行截图:
遇到的问题 1
遇到的问题 2
遇到的问题 3
其它线程文章
栗子 一 、生产者/消费者 模式对应的关系,以及实现。
栗子 二 、生产者/消费者 模式一个生产者多个消费者的具体实现以及对应关系。
项目结构:
mainwindow 是界面UI实现文件
mythread 是线程实现文件
这是基本实现线程的一篇博客可以忽略 QT线程简单实现
栗子 一、 QSemaphore 线程同步 生产者/消费者
调用实现
先上次要代码,调用代码。
//引入头文件
#include "mythread.h"
//赋值
mythread *thread;
WriterThread *consumer;
Consumer *writer;
//调用
void MainWindow::on_pushButton_2_clicked()
{
thread->start();
consumer->start();
thread->wait();
consumer->wait();
}
初始化全局变量
synchronize_data.h
//
// Created by qiaowei on 2023-10-28.
//
#ifndef CPPGUIPROGRAMMINGWITHQT4INCLION_SYNCHRONIZE_DATA_H
#define CPPGUIPROGRAMMINGWITHQT4INCLION_SYNCHRONIZE_DATA_H
#include <QChar>
#include <QSemaphore>
/**
* @date 2023-10-28
* @author qiao wei
* @brief 要传输的总数据数量。
*/
extern const int kDataSize;
/**
* @date 2023-10-28
* @author qiao wei
* @brief 数据池容量。
*/
extern const int kBufferSize;
/**
* @date 2023-10-28
* @author qiao wei
* @brief 数据池。初始阶段,数据池未存放任何数据
*/
extern QChar buffer[];
/**
* @date 2023-10-30
* @author qiao wei
* @brief 自由信号标。在初始阶段有kBufferSize个自由信号标可用。数据池每放入1个数据,自由信号标就减1个。
*/
extern QSemaphore free_semaphore;
/**
* @date 2023-10-30
* @author qiao wei
* @brief 可用信号标。在初始阶段没有数据可用,所以可用信号标数量为0。当数据池放入1个还未用的数据时,可用信号标就增加1个。
*/
extern QSemaphore used_semaphore;
#endif //CPPGUIPROGRAMMINGWITHQT4INCLION_SYNCHRONIZE_DATA_H
synchronize_data.cpp
//
// Created by qiaowei on 2023-11-01.
//
#include "synchronize_data.h"
const int kDataSize{1000};
const int kBufferSize{4};
QChar buffer[kBufferSize];
QSemaphore free_semaphore{kBufferSize};
QSemaphore used_semaphore{0};
实现生产者线程
producer.h
//
// Created by qiaowei on 2023-10-28.
//
#ifndef CPPGUIPROGRAMMINGWITHQT4INCLION_PRODUCER_H
#define CPPGUIPROGRAMMINGWITHQT4INCLION_PRODUCER_H
#include <QThread>
namespace synchronize_thread {
class Producer : public QThread {
public:
explicit Producer();
virtual ~Producer() override;
protected:
/**
* @date 2023-10-30
* @author qiao wei
* @version 1.0
* @brief
* @param
* @return
* @throws
*/
virtual void run() override;
};
} // synchronize_thread
#endif //CPPGUIPROGRAMMINGWITHQT4INCLION_PRODUCER_H
producer.cpp
//
// Created by qiaowei on 2023-10-28.
//
#include <QChar>
#include "producer.h"
#include "synchronize_data.h"
namespace synchronize_thread {
Producer::Producer() :
QThread() {
}
Producer::~Producer() {}
void Producer::run() {
/**
* 遍历总数据量,将随机数据生成后存放到数据池。
* 自由信号标数量减少1位。说明自由信号标被选出的随机字符占用1位。初始时自由信号标总数为40个。
* 循环时将1个随机字符放到数据池。
* 可用信号标数量增加1位。数据池中新增1个随机字符,可用信号标增加1个。
*/
for (int index{0}; index != kDataSize ; ++index) {
free_semaphore.acquire();
buffer[index % kBufferSize] = QChar{"ACGT"[(std::rand() % 4)]};
used_semaphore.release();
}
}
} // synchronize_thread
实现消费者线程
consumer.h
//
// Created by qiaowei on 2023-10-28.
//
#ifndef CPPGUIPROGRAMMINGWITHQT4INCLION_CONSUMER_H
#define CPPGUIPROGRAMMINGWITHQT4INCLION_CONSUMER_H
#include <QThread>
namespace synchronize_thread {
class Consumer : public QThread{
public:
explicit Consumer();
virtual ~Consumer() override;
protected:
virtual void run() override;
};
} // synchronize_thread
#endif //CPPGUIPROGRAMMINGWITHQT4INCLION_CONSUMER_H
consumer.cpp
//
// Created by qiaowei on 2023-10-28.
//
#include <iostream>
#include <QtDebug>
#include "consumer.h"
#include "synchronize_data.h"
using std::cout;
namespace synchronize_thread {
Consumer::Consumer() :
QThread() {
}
Consumer::~Consumer() {
}
void Consumer::run() {
/**
* 从数据池取数据。
*/
for (int index{0}; index < kDataSize; ++index) {
used_semaphore.acquire();
qDebug() << buffer[index % kBufferSize].toUpper() << Qt::endl;
free_semaphore.release();
}
}
} // synchronize_thread
栗子 二、 QSemaphore 线程同步 一个生产者多个消费者
调用实现
先上次要代码,调用代码。
void TestSemaphore::testSemaphore() {
Producer* producer{new Producer{}};
Consumer* consumer{new Consumer{}};
producer->start();
consumer->start();
producer->wait();
consumer->wait();
}
那就是在停止线程前先执行thread->quit()停止线程,先停止线程在结束线程,否则很有可能在你wait结束完线程之后你的线程还会跑一会。
初始化全局变量
//互斥锁
QMutex mutex;
//单个生产者消费者使用变量
const int DataSize = 60;
const int BufferSize = 10;
//char buffer[BufferSize];
//产品数量
QSemaphore freeSpace(BufferSize);
QSemaphore usedSpace(0);
//缓冲区可以放10个产品
int buffer[10];
int numtaken=60;
int takei=0;
实现生产者线程
void mythread::run()
{
for (int i = 0; i < DataSize; ++i)
{
freeSpace.acquire();
qDebug() << "生产:thread "<<QThread::currentThreadId();
usedSpace.release();
//暂停一下 不然闪屏太快
//已经500度的眼睛了,近视加散光别在老花眼
sleep(1);
}
}
实现消费者线程
void WriterThread::run()
{
//函数获取当前进程的ID
while(numtaken>1)
{
usedSpace.acquire(); //P(s2)操作原语
mutex.lock(); //从缓冲区取出一个产品...多个消费者,不能同时取出,故用了互斥锁
printf("thread %ul take %d from buffer[%d] \n",currentThreadId(),buffer[takei%10],takei%10);
qDebug() << "消费:thread "<<QThread::currentThreadId()<<"take "+QString::number(takei%10)+" from buffer["+QString::number(buffer[takei%10])+"] \n";
takei++;
numtaken--;
mutex.unlock();
usedSpace.release(); //V(s1)操作原语
//sleep(1);
}
}
QMutex类提供的是线程之间的访问顺序化。
QMutex的目的是保护一段代码,使得同一时间只有一个线程可以访问它。但在一个线程中调用lock(),其它线程将会在同一地点试图调用lock()时会被阻塞,直到这个线程调用unlock()之后其它线程才会获得这个锁。
信号量QSemaphore可以理解为对互斥量QMutex功能的扩展,互斥量只能锁定一次而信号量可以获取多次,它可以用来保护一定数量的同种资源。acquire()函数用于获取n个资源,当没有足够的资源时调用者将被阻塞直到有足够的可用资源。release(n)函数用于释放n个资源。
运行截图:
因为把sleep注释了,所以可以看到截图内的生产者提前生产了,这个属于正常现象,对程序影响不大,也有办法解决。
在消费者线程起来了之后,可以看到在消费者代码中如果代码没执行完 freeSpace生产者线程是不会释放的,会阻塞生产者线程。
达到一生产一消费的状态。
遇到的问题 1
如何实现生产一个多个消费,下图为何执行的丝般顺滑??? 如果你按照我上图的做了,同时看到这里发现执行不出来,请往下看
遇到这个问题最大的原因就是因为 release 和 acquire 两个参数的原因,因为创建和销毁的时机不一样所以才会导致你的代码没有图片的丝般顺滑。
遇到的问题 2
如果没有release怎么办,答案是程序假死,一直在等release,没等到release就不走,赖到那了,不给钱起不来
其它线程文章
以下文章均为作者原创文章,看完记得收藏、关注加👍
线程、进程、多线程、线程池一文看懂从此秒变大佬!:https://blog.csdn.net/qq_37529913/article/details/115533429
QT 初识线程(简单实现):https://blog.csdn.net/qq_37529913/article/details/110127940
QT QMutex使用详解:https://blog.csdn.net/qq_37529913/article/details/110187452
QT 线程之QSemaphore(深入理解):https://blog.csdn.net/qq_37529913/article/details/110187121
QT线程 Emit、Sgnals、Slot详细解释:https://blog.csdn.net/qq_37529913/article/details/110211435
QT 线程之QWaitCondition(深入理解):https://blog.csdn.net/qq_37529913/article/details/110212704
Qt 多线程之线程事件循环(深入理解):https://blog.csdn.net/qq_37529913/article/details/110229382
QT线程之QObjects(深入理解):https://blog.csdn.net/qq_37529913/article/details/110228837
QT线程之可重入与线程安全(深入理解):https://blog.csdn.net/qq_37529913/article/details/110224166
QT 主线程子线程互相传值:https://blog.csdn.net/qq_37529913/article/details/110506178
QT线程同步与异步处理:https://blog.csdn.net/qq_37529913/article/details/110521759
QT 多线程之线程池QThreadPool(深入理解):https://blog.csdn.net/qq_37529913/article/details/115536799
QT之浅拷贝、深拷贝、隐式共享(深度理解必看文章):https://blog.csdn.net/qq_37529913/article/details/110235596
QT 隐式共享机制对STL样式迭代器的影响:https://blog.csdn.net/qq_37529913/article/details/110252454