qt QThread、QMutex、QSemaphore

QThread:

创建线程(2种方式):

1:

  1.1 新建一个继承 QTherad 的类myTherad(不能有ui),重写 run() 函数(真正新线程处理的地方,因为不方便随时调用所以一般是个while循环) 。

  1.2 主线程 new 上面的那个类 myTherad* _myTherad =  new myTherad(this)

  1.3 开始运行 run() : _myTherad.start();

2(默认支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer、QTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程): 

  2.1 先随意新建一个类 classTs继承QObject就好,类里面有些方法

  2.2 classTs* ts = new classTs; // 这里不能用this,因为你整个是要给子线程的

  2.3 QThread* _myTherad = new QThread(this); // 创建子线程

  2.4 ts->moveToThread(_myTherad ); // 把你随意建的类移动到子线程里面去,现在那个类就是属于子线程的人了

  2.5 _myTherad->start(); // 开启线程

现在开始classTs类里的函数就都是在子线程里面运行了,至于怎么触发classTs类的函数,一般用型号槽,比如我点击主页面的一个按钮执行classTs类里的 a 函数,a函数就是在子线程里执行的了

 

结束线程:

在run函数调用exit()或者quit()函数可以结束线程,或在主线程调用terminate或stopImmediately()强制结束线程。

// 主函数结束子线程:

_myThread->requestInterruption(); // 最好加上让线程自己走完
 _myThread->terminate(); // 强制结束,它没全部跑完也没关系
 _myThread->wait();  // 等它完全结束
delete _myThread;
_myThread = NULL;
// 局部线程结束:

Q_SIGNALS:    
void started(QPrivateSignal);    //开始前发射
void finished(QPrivateSignal);  //结束时发射该信号

// 局部线程:
m_printThread = new QPrintThread(NULL);//局部线程,没有父对象
m_printThread->start();

// 退出:
m_printThread->stopImmediately();

connect(m_printThread,&QPrintThread::finished,m_printThread,&QObject::deleteLater);//自动销毁分配的内存

connect(m_pSaveThread,&SaveDataThread::destroyed,this,&MainWindow::SaveDataThreadDestroy);  //SaveDataThreadDestroy自定义的槽函数

// 用一个临时变量记录当前正在运行的局部线程,由于线程结束时会销毁自己,因此要通知主线程把这个保存线程指针的临时变量设置为NULL
  因此用到了QObject::destroyed信号,在线程对象析构时通知UI把m_printThread 设置为nullptr;

void MainWindow::SaveDataThreadDestroy(QObject *obj)
{  
  if(qobject_cast<QObject*>(m_pSaveThread) == obj )   {  m_pSaveThread = NULL;  }
}

全局线程和局部线程的区别

1.全局线程需要调wait阻塞等待,防止主线程窗口销毁了,把线程对象也释放了,而线程还没出来,会引起崩溃。

2.局部线程退出时,需要通知主线程把线程指针置NULL

 

QMutex:

void myThread::run() {

    qDebug() << "myThread run start.";

    int a = 0;
    while (!isInterruptionRequested())
    {
     // QMutex会带来一定的耗时,大概速度会降低1.5倍(Release模式)
        mutex.lock(); // 确保同一时间只有一个线程可以运行lock与unlock之间的内容
        sleep(1);
        a++;
        qDebug() << "    myThread run while:" << a  << "  currentThreadId:" << currentThreadId()  ;
        mutex.unlock();
        sleep(1);
    }

    qDebug() << "myThread run end.";
}

// 主线程

_myThread->mutex.lock(); // 主线程得到锁,那 myThread 就一直卡在 mutex.lock() 直到得到锁, 一把锁只能一个人得到,得到者才能操作。 QMutexLocker lock(&mutex) 得到加完了会自动解锁

/*
lock: 阻塞式的,得不到锁就一直等着得到它,只有等别人 unlock 才能得到
tryLock: 非阻塞式。如果获取锁失败(即锁已被其他线程获取),立即返回false否则立即返回true。传参得话,表示的是时间,加了就根据时间来了,就不立即了返回了

QWaitCondition:
    wait(&mutex):主动解锁,然后把自己阻塞,等着 wakeAll 或wakeOne(唤醒一个等待线程,具体哪一个线程,由操作系统决定) 来解救才继续往下执行.
    所以调用wait函数前,必须是要有锁的

QReadWriteLock: 读写锁
    特点:可以多个线程以只读方式访问,但只有一个线程以写的方式访问。
    LockForRead();   如果有其他线程以写入的方式锁定,才阻塞自己
    LockForWrite();   阻塞自己
    注:QReadlocker 和QWriteLocker是QReadWriteLock的简化,如同QMutexLocker  是QMutex   的简化一样。无须使用unlock().

*/
// 生产者消费者 这种就是同一时刻只能有一个线程操作数据(写或者读)

// 全局变量
QMutex mutex;
QWaitCondition bufferEmpty;
QWaitCondition bufferFull;

const int DataSize = 1000;
const int BufferSize = 80;
int buffer[BufferSize];

int numUsedBytes = 0; // 已经生产的数据
int rIndex = 0;

// 生产者
void Producer::run()
{
    for (int i = 0; i < DataSize; i++)
    {
        mutex.lock();
        if (numUsedBytes == BufferSize)
            bufferEmpty.wait(&mutex);   //容量已满,先解锁mutex,让其它线程能够使用mutex,然后自己阻塞,等着wakeAll
        buffer[i%BufferSize] = i%BufferSize;
        numUsedBytes++;      //用来记录当前缓冲区是否已满
        bufferFull.wakeAll();        //缓冲区有容量,唤醒消费者
        mutex.unlock();
    }
}

// 消费者
void Comsumer::run()
{
    for (int i = 0; i < DataSize; i++)
    {
        mutex.lock();
        if (numUsedBytes == 0)
            bufferFull.wait(&mutex);  //容量为空,阻塞消费者   
        qDebug() << currentThreadId() << buffer[i%BufferSize];
        numUsedBytes--;
        bufferEmpty.wakeAll();     //缓冲区未满,唤醒生产者
        mutex.unlock();
    }
}

 

 

QSemaphore: 一个通用的可计数的信号量,信号量实际上就是广义的互斥量。一个互斥量只能被锁定一次,然而一个信号量可以被获取多次。信号量常被用于保护一定数目的同类资源。

基本操作:

QSemaphore::QSemaphore ( int n = 0 ) : 新建一个信号量,守护的资源数为n(默认为0)

QSemaphore::~QSemaphore ():销毁信号量警告:销毁一个正在使用的信号量将导致一个不确定的行为

int QSemaphore::available() const :用于返回可用资源的数目

void QSemaphore::acquire ( int n = 1 ):从该信号量守护的资源中获取n个资源。如果获取的资源数目大于available()返回值,那么自己将阻塞,直到可获取足够的资源数

void QSemaphore::release ( int n = 1 ):释放n个信号量守护的资源给信号量。该函数也可以用来增加一个信号量可使用的资源数目

bool QSemaphore::tryAcquire ( int n = 1 ):尝试从信号量守护的资源中获取n个资源。成功,则返回true。如果当前可用的资源数目不够立即返回,返回值为false,并且不占用任何资源

bool QSemaphore::tryAcquire ( int n, int timeout ):尝试从信号量守护的资源中获取n个资源。成功,则返回true。如果当前可用的资源数目available()不够,将等待timeout微秒。

例子:

QSemaphore sem(5); // sem.available() == 5
sem.acquire(3); // sem.available() == 2
sem.acquire(2); // sem.available() == 0
sem.release(5); // sem.available() == 5
sem.release(5); // sem.available() == 10
sem.tryAcquire(1); // sem.available() == 9, returns true
sem.tryAcquire(250); // sem.available() == 9, returns false
// 生产者消费者 这种就是同一时刻可以有多个线程同时操作数据(一边写一边读)


// 全局函数(使用类的static/extern(注意.h声明 .cpp定义)/线程类在同一文件)

const int DataSize = 1000; //生产者将生成的数据量
const int BufferSize = 800; //循环缓冲区的大小,该值小于DataSize
char buffer[BufferSize];
QSemaphore freeData(BufferSize); // 空闲的可供生产的数据,就是假设消费者一直抢不到cpu那生产者最多也只能生产到800就停止了,只有等消费者消费了一个生产者才能生产第二轮的第一个
QSemaphore usedData(0); // 可供读取使用的数据


// 生产者线程
void Producer::run(){
    for (int i = 0; i < DataSize; ++i) {
        freeData.acquire(); // 生产完了一个,这样生产的可用资源少一个 799 ...... 
        qDebug() << "生产:thread " << QThread::currentThreadId() ;
     buffer[i%BufferSize] = QString::number(i).at(0).unicode(); usedData.release();
// 这样消费者的资源多一个 1 ...... } } // 消费者线程 void Consumer::run(){ for (int i = 0; i < FxGlobal::DataSize; ++i) { usedData.acquire(); // 消费完了一个,这样消费者的资源少一个 0 ...... qDebug() << "消费:thread " << QThread::currentThreadId() ;
   fprintf(stderr, "%c", buffer[1%BufferSize]); freeData.release();
// 这样生产的可用资源多一个 800 ...... } }
// 一个生产者多个消费者

// 初始化全局变量

//互斥锁
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();

        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);
    }
}

 

posted @ 2023-03-28 17:51  封兴旺  阅读(272)  评论(0编辑  收藏  举报

联系方式: 18274305123(微信同号)