Qt多线程

一、QThread类:为Qt中所有线程的基类。

  1. QThread常用方法
函数名功能
start()槽函数,调用该方法后,会发出started()信号,且之后会执行run()方法
run()为虚函数,子类派生于QThread会重写run()方法,该方法就是线程要执行的内容
wait()用于阻塞,知道线程结束才返回
setPriority()设置线程的优先级,注意优先级必须在run()方法里设置,因为main()中设置不了
isFinished()线程是否结束
isrunning()线程是否在运行
terminate()槽函数,强制终止线程,可以在线程的任何阶段被终止,所以可能导致线程资源无法清理。(不建议用该函数)
  1. QThread的静态成员以及常用信号
静态方法功能
currentThreadId()获取当前的线程Id
sleep()使得当前线程睡眠多少秒
msleep()使得当前线程睡眠多少毫秒
usleep()使得当前线程睡眠多少微秒
常用信号功能
started()线程开始后发出该信号。
finished()线程执行结束后发出此线号。

创建线程的常见方式

方式一:创建一个子类继承于Qthread,并重写虚函数run()
	class Mythread: public QThread{
	       Q_OBJECT
        public:
            MyThread(){ };
            void run() override;
        private:
}
void MyThread::run(){
    setPriority(优先级的枚举);  //如果设置优先级,就必须在run方法里设置优先级,
    							//main函数中设置不了,因为只有线程运行起来才能设置优先级。
    qDebug()<< QThread::currentThreadId(); 
    msleep(毫秒); //可以是CPU不那么忙。
 }

int main(){
	MyThread my;
     my.start(); //开始启动线程;
     my.wait(); 	
}
方式二:创建一个子类,定义我们线程要执行的内容(这里可以定义多个函数)
class Worker : public object{
     Q_OBJECT
     public slots: //这两个槽函数,用于执行多线程的业务逻辑
          void Do(){
          }
          void Work(){
          }
}
int main(){
    Worker * worker=new Worker(); //定义worker类对象,
    QThread *t=new QThread();  //定义线程;
    worker->moveToThread(t); //将worker加入到线程中
    t->start(); //这行必须要,因为只有启动了t这个线程, worker收到信号才能反应
    connect(this,信号A,w,&Worker::Do);//用信号A触发Do函数来执行多线程
    connect(this,信号B,w,&Worker::Work); //用信号B触发Work函数来执行多线程
    //Do和work是在同一个线程中
}

二、线程的互斥与互锁

    1. QMutex类:一个线程互斥锁,多线程访问同一个资源时时,难免会出现冲突,互斥锁可以保证多个线程之间执行互斥

 QMutex mm;//这个互斥锁对象必须是全局变量
 void MyThread::run() {
     while(ticktes>0){          
		mm.lock();     //加锁  ,使得同类线程对象A,B互斥,在这个所内A和B线程只能执行一个
		QThread::msleep(1);        
		qDebug()<<QStringLiteral("线程")<<name<<QStringLiteral("卖票号:")<<ticktes;                  
		ticktes;
		mm.unlock();    //解锁,解锁后A,B线程不在互斥,等到下次加锁在互斥
    }
 }

QMutex互斥:lock和unlock之间的代码,可以看成是一个最小执行单位,如果A线程执行了这个最小单位,那么就必须等A执行完这个最小单位,其他同类对象的线程B才能执行这个最小单位。

    2. QMutexLocker类:是一个更方便的互斥类,它需要结合QMutex搭配,用于自动锁定和互斥解锁,不需要手动解锁。

	QMutex mutex;//mutex为全局变量
	void MyThread::run(){
		while(tickets>0){
			QMutexLocker mlck(&mutex);
			//·········
			//·········
			//·········		
		}
	}

QMutexLocker互斥:QMutexLocker的互斥锁默认是它外面一层的作用域,如上的while循环的{ },就是作用域,在这个作用域自动加锁和解锁。

    3. QRecursiveMutex: 是一个递归互斥锁类,继承与QMutex,它运行一个线程多次加锁和解锁,但是加锁和解锁次数保持一致。

不仅可以满足同类型线程的不同对象线程互斥,还能满足同一线程内可以多次加锁,解锁。(如:线程内的fun1(),fun2()加锁后,,那么fun2()调用fun1(),由于fun2()有锁,如果这个锁是QMutex类对象就会卡死,这 时候就可以使用QRecursiveMutex)/

QRecursiveMutex mm;//不会卡死,如果是QMutex mm,那么就会阻塞
void MyThread::run(){ 
      qDebug()<<name<<"run被执行"; 
      fun2();   
      qDebug()<<name<<"run结束";
} 
void MyThread::fun1() {   
       mm.lock();     
       qDebug()<<"fun1被执行 " ; 
       mm.unlock();
 } 
 void MyThread::fun2() {  
     mm.lock();
     qDebug()<<"fun2被执行";    
     fun1();   
     mm.unlock(); 
}

QRecursiveMutex 与 QMutex的区别:QMutex:保证了线程间的互斥,但缺点是在 同一作用域内不能多次加锁和解锁。QRecursiveMutex: 可以实现不同线程互斥,还可以解决QMutex的缺点。

  4. QSemaphore类: 可以理解为互斥量QMutex类的增强版,QSemaphore类互斥量只能获取一次,而信号资源可以获取多次,可以保护一定数量的同种资源
QSemaphore的常用方法

函数名功能
QSemaphore(int n)构造时,传入被访问的资源数量n ,可以允许n个资源同时被使用
availavle返回信号量当前可以用的资源数
acquire(int n)尝试获取n个资源。如果n>available(), 这个调用会阻塞,直到有足够的资源可用
release(int n)释放用信号两边保护的n个资源,

  举例子,就是有一个停车场有3个车位,外面有n辆车想要停在停车场,停车场一下子停不了n辆,最多只能停3辆,停车场里的车离开几辆,外面的车才能进来几辆。(停车场里的3个车位就比如多个允许的线程资源,外面的总线程)

extern QSemaphore sem(3);   //全局变量中声明,表示允许3个线程同时访问资源。
void CarThread::run(){
    while(true){
       sem.acquire();// 减少一个线程资源数。 如果n>3,那么将会阻塞。
       qDebug()<<name<<QStringLiteral("进入车位!")QThread::run(); //停车时间;
      qDebug()<<name<<QStringLiteral("离开车位!");   
       sem.release(); //释放一个车位;即在次增加一个线程资源数。
    }
}

三、线程同步

  QWaitCondition类:允许一个或多个线程可以阻塞 wait() 等待执行,由**WakeOne()WakeAll()**去唤醒一个或多个线程。
  应用场景模拟:现有一个仓库,可以容纳3个商品。我们建立3个生产者线程不断往仓库放商品,1个消费者线程慢慢从仓库里每次取一个商品。(供大于求)
  解决方案:当仓库满了,调用QWaitCondition的wait()方法使得3个生产者线程全阻塞,直到消费者拿出一个商品,消费者发出WakeAll()方法,生产者才继续生产。

QWaitCondition goProducer;   //用于阻塞线程,一般放在全局
QMutex mutex; // 用于线程间的互斥;
goProducer.wait(&mutex);  //使得线程阻塞
goProducer.wakeAll();//唤醒所有之前在wai的线程
//goProducer.wakeOne();//随机唤醒一个阻塞的线程

四、线程池

  QRunnable类:为一个可执行的类的抽象类(其中run()为纯虚函数),因此,需要派生类需要重写run()方法。
  QThreadPool类:由于线程的创建和销毁需要很大的CPU开销。如果一个程序频繁的创建和销毁线程,那么将极大的影响CPU的性能。那就引出了线程池,将一定数量的线程放入池中进行统一管理,当需要进行多线程运算时将运算任务扔给线程池,线程池自动分配线程来处理。
   线程池的优点
   1. 降低资源开销:线程池通过重复利用已有的线程,避免频繁创建和销毁线程所产生的开销。
   2. 提高响应速度:当任务到达时,可以不需要等待线程创建,立即执行。
   3. 通过线程的利用率: 使用线程池可以进行统一的分配,监控和调优。
在这里插入图片描述
QThreadPool类的常用方法

函数名功能
setMaxThreadCount设置线程池的最大线程数量
start加入一个任务(QRunnable的派生类)到线程池中并执行,如果线程池数量达到最大,则放入运行队列中等待执行
activeThreadCount返回线程池中的活跃线程个
waitForDone( int n )等待线程池中所以线程任务执行完毕,未超时返回true,超时返回false。如果是 n=-1(默认值),永不超时
QThreadPool pool;
Class MyTask:public QRunnable
{
    public:
            MyTask(QString name){
            this->name=name;
            }
            ~MyTask(){
             qDebug()<<name<<"任务结束";

            }
            void run(){
                qDebug()<< name <<Stringliteral("开始执行任务")<<QThread::currentID();   
            }
    private:
            QString name;
}
void ZhixingYouxianji(){
    for(int i=1;i<=10,i++){
        MyTask* t=new MyTask(QStringliteral("任务").arg(i));
        pool.start(t,i); //启动第i个线程,这个i越大,优先级就越高。但是有一点要注意,我们在下面有说明
    }
    //等待线程池中的所有任务都执行完毕才结束执行,否则就阻塞
    int n=10;//
    pool.waitForDone(n);//等待10s, 如果括号内传入-1,代表永远等待,界面就会卡住,
   qDebug()<<"函数执行结束";

}

注意: start(t,i) 这个i越大,说明这个t线程优先级越高,但是有个前提,这个线程是在等待队列中。所以优先级是在队列中进行排列的。 如果一个空线程池,这些线程没有进入队列等待,那么线程的执行就按线程加入线程池的先后顺序来执行。

参考黄强老师的多线程编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值