2009-10-06 15:42:00 xgjianstart 阅读数 4980

linux中的线程是轻量级的线程,linux中的线程调度是由内核调度程序完成的,每个线程有自己的ID号。与进程相比,它们消耗的系统资源少、创建较快、相互间的通信也较容易。linux线程分为两类:一是核心级支持线程,二是用户级的线程。一般都为用户级的线程。

一、多线程的几个常见函数

要创建多线程必须加载pthread.h文件,库文件pthread。线程的标识符pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:typedef  unsigned  long  int  pthread_t

1.创建线程:

int pthread_create(pthread_t *restrict thread,
           const pthread_attr_t *restrict attr,
           void *(*start_routine)(void*), void *restrict arg);
参数:
      thread输出线程id
     attr 线程属性, 默认NULL
      start_routine线程执行函数
      arg线程执行参数  
note:函数成功返回0 否则返回错误码
2.等待指定线程结束:
int pthread_join(pthread_t thread,void **value_ptr);
参数:
      thread一个有效的线程id
      value_ptr 接收线程返回值的指针
note:调用此函数的线程在指定的线程退出前将处于挂起状态或出现错误而直接返回,如果value_ptr非NULL则value_ptr指向线程返回值的指针,函数成功后指定的线程使用的资源将被释放。
3.退出线程:
int pthread_exit(void * value_ptr);
参数:
      value_ptr 线程返回值指针
note: ptrhead_exit()退出调用此函数的线程并释放该线程占用的资源。
4.获取当前线程id:
pthread_t pthread_self(void);
参数:      
note:返回当前函数的id
5.互斥
创建互斥:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                     const pthread_mutexattr_t *restrict attr);
参数:
      mutex输出互斥id
     attr 互斥属性, 默认NULL
note:函数成功返回0 否则返回错误码
锁住互斥:
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
      mutex互斥id
note:如果指定的互斥id已经被锁住那么呼叫线程在互斥id完全解锁前将一直处于挂起状态,否则将锁住互斥体。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数:
      mutex互斥id
note:如果指定的互斥id已经被锁住那么将直接返回一个错误,通过判断此错误来进行不同的处理。pthread_mutex_trylock和pthread_mutex_lock相似,不同的是pthread_mutex_trylock只有在互斥被锁住的情况下才阻塞。
解锁互斥:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
     mutex互斥id
note:如果指定的互斥id已经被锁住那么对其解锁
释放互斥:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
     mutex互斥id
note:释放指定的mutex占用的资源。
函数pthread_mutex_init和pthread_mutex_destroy分别是互斥锁的构造函数和析构函数。
 二、多线程同步
1.互斥体
互斥量(mutex)相当于一把锁,可以保证以下三点:
◎原子性:如果一个线程锁定一个互斥量,那么临界区内的操作要么全部完成,要么一个也不执行。
◎惟一性:如果一个线程锁定一个互斥量,那么在它解除锁定之前,没有其他线程可以锁定这个互斥量。
◎非繁忙等待:如果一个线程已经锁定一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止。
2.条件变量
条件变量是一种可以使线程(不消耗CPU)等待某些事件发生的机制。某些线程可能守候着一个条件变量,直到某个其他的线程给这个条件变量发送一个信号,这时这些线程中的一个线程就会苏醒,处理这个事件。但条件变量不提供锁定,所以它必须与一个互斥量同时使用,提供访问这个环境变量时必要的锁定。
3.信号量
Dijkstra提出了信号量的概念,信号量是一种特殊的变量,只可以取正整数值,对这个正整数只能采取两种操作:P操作(代表等待,关操作)和V操作(代表信号,开操作)。
P/V操作定义如下(假设我们有一个信号量sem) :
P(sem):如果sem的值大于0,则sem减1;如果sem的值为0,则挂起该线程。
V(sem):如果有其它进程因等待sem而挂起,则让它恢复执行;如果没有线程等待sem而被挂起,则sem加上1。
信号集的创建与打开
int semget(key_t key,int nsems,int flag);
对信号量的操作
int semop(int semid,struct sembuf semoparray[],size_t nops);
对信号量的控制
int semctl(int semid,int semnum int cmd,union semun arg);
附:经典的生产者-消费者问题(Producer-Costomer)是一个著名的同步问题。
2014-03-16 21:00:47 shamofeiyu 阅读数 3045

在比较大型的项目中,通常都会使用多线程技术,而且通常是多人合作开发,各方自测OK之后,整合在一起往往会出现一些问题,CPU使用率过高就是其中之一。如何在不熟悉所有模块代码的情况下,快速的定位到具体哪一个线程在消耗CPU,显得很有必要。在X86上,可以借助一些工具进行定位分析,但是在嵌入式系统中,工具就比较匮乏,各命令功能也比较简单,就不好定位。现介绍一种简单通用的办法:

第一步:

获取各个线程的tid,Linux环境下的线程其实就是轻量级的进程,只不过通过top\ps等常用命令一般都查不到具体的线程号tid(指在嵌入式系统中),需要在各个线程实现代码中获取线程ID。

#include <sys/syscall.h> 

pid_t gettid()
{
     return syscall(SYS_gettid);   
}
参考自:Linux打印真实pid的方法 http://blog.csdn.net/gaoxuelin/article/details/9718189


第二步:

通过线程ID获得各线程的CPU使用率。

主要是通过分析/proc/<pid>/task/<tid>/stat文件获得,pid为程序的PID,tid为程序的各个线程的ID号(就是第一步输出的线程ID),stat文件就是一些调度的基本信息,具体可参阅:Linux proc/pid/task/tid/stat文件详解 http://blog.csdn.net/ctthuangcheng/article/details/18090701

线程比较多的时候一个线程一个线程去分析该文件比较费劲,可通过脚本一次解析完成,参数为进程PID,运行成功会输出该进程的所有线程tid、用户层CPU使用、内核态CPU使用,数值越高表示消耗CPU资源越多。

#!/bin/sh
#get /proc/pid/task/tid/stat
#$1 is tid
#$14  is user cpu 
#$15 is sys cpu
echo "tid user sys"
for file in /proc/$1/task/*
do
    if test -d $file
    then
        cat $file/stat | awk -F" " '{print $1 " " $14 " " $15}'
    fi
done



2019-08-21 16:13:50 Z_Silence 阅读数 359

《从实践中学嵌入式Linux应用程序开发》
在这里插入图片描述
内容简介:

 《从实践中学嵌入式linux应用程序开发》结合大量实例,讲解了嵌入式linux应用程序设计各个方面的基本方法及必要的核心概念。主要内容包括搭建嵌入式linux开发环境、嵌入式文件i/o编程、嵌入式linux多任务编程、嵌入式linux进程间通信、嵌入式linux多线程编程、嵌入式linux网络编程、嵌入式linux设备驱动编程、android应用编程、android播放器项目设计等。重视应用是贯穿全书的最大特点,在各章和全书结尾分别设置了在项目实践中常见和类似的应用实例。

本书可作为大学院校电子、通信、计算机、自动化等专业的嵌入式linux开发课程的教材,也可供嵌入式开发人员参考。学习本书应具有linuxc语言编程和java编程的基本知识。

图书目录:

第1章 搭建嵌入式linux开发环境
1.1 构建嵌入式linux交叉开发环境
1.2 bootloader
1.3 linux内核与移植
1.4 嵌入式文件系统构建
第2章 嵌入式文件i/o编程
2.1 linux系统调用及用户编程接口(api)
2.2 linux文件i/o系统概述
2.3 底层文件i/o操作
2.4 嵌入式linux串口应用编程
2.5 标准i/o编程
2.6 实验内容
第3章 嵌入式linux多任务编程
3.1 linux下多任务机制的介绍
3.2 进程控制编程
3.3 实验内容
第4章 嵌入式linux进程间通信
4.1 linux下进程间通信概述
4.2 管道通信
4.3 信号通信
4.4 信号量
4.5 共享内存
4.6 消息队列
4.7 实验内容
第5章 嵌入式linux多线程编程
第6章 嵌入式linux网络编程
6.1 tcp/ip协议概述
6.2 网络编程基础
6.3 网络高级编程
6.4 实验内容——ntp协议的客户端实现
第7章 嵌入式linux设备驱动编程
7.1 设备驱动编程基础
7.2 字符设备驱动编程
7.3 gpio驱动程序实例
7.4 按键驱动程序实例
第8章 android应用编程
8.1 android发展简史
8.2 搭建android应用开发环境
8.3 android的四大组件和intent
8.4 android常用图形界面组件
第9章 android播放器项目设计
9.1 简易音乐播放器应用的需求
9.2 界面设计
9.3 播放器控制和播放功能的实现
9.4 项目运行

没法上传文件,有需要的留个邮箱吧,或者私我,也可以加 765573562 找管理要

其他视频资料:

linux基础
http://www.makeru.com.cn/course/details/2058?s=96806

快速上手linux
http://www.makeru.com.cn/live/1758_310.html?s=96806

2019-04-22 14:48:56 wenjs0620 阅读数 215

本文的内容是拜读完以下文章后的总结,喝水不忘挖井人,感谢前辈的肩膀,让我们这些晚辈少走弯路,走得更远。如果已经理解了原作者的文章,则可完全忽略本文,感谢支持和关注。

https://blog.csdn.net/czyt1988/article/details/71194457

上一篇文章介绍了使用继承QThread类,重载run()函数的方法来实现多线程,这种方法是QT实现多线程编程的传统方法,但从QT4.8以后的版本开始,QT官方并不是很推荐使用这种方法,而是极力推荐使用继承QObject类的方法来实现多线程,因为QObject类比QThread类更加灵活。当然,使用哪种方法实现多线程,需要根据具体的实际情况而定。

关于上一篇文章,请点击这里。https://blog.csdn.net/wenjs0620/article/details/89331788

QObject类是QT框架里面一个很重要的基本类,除了QT的关键技术信号与槽,QObject还提供了事件系统和线程操作接口的支持。对QObject类,可以使用QObject类里面的方法moveToThread(QThread *targetThread),把一个没有父级的继承于QObject的类转移到指定的线程中运行。

使用继承QObject来实现多线程,默认支持事件循环(QT里面的QTimer、QTcpSocket等等,均支持事件循环)。而如果使用QThread类来实现多线程,则需要调用QThread::exec()来支持事件循环,否则,那些需要事件循环支持的类都无法实现信号的发送。因此,如果要在多线程中使用信号和槽,那就直接使用QObject来实现多线程。

不管使用方法一还是方法二,在QT中创建多线程是比较简单的,难点在于如何安全地退出线程和释放线程资源。在创建完线程之后,使用方法二(继承QObject)来创建的线程,不能在ui线程(主线程)中直接销毁,而是需要通过deleteLater() 进行安全销毁。

先来总结一下,如何使用继承QObject的方法来创建多线程:

(1)写一个继承QObject的类,并把复杂耗时的操作声明为槽函数。

(2)在主线程(ui线程)中new一个继承Object类的对象,不设置父类。

(3)声明并new一个QThread对象。(如果QThread对象没有new出来,则需要在QObject类析构的时候使用QThread::wait()等待线程完成。如果是通过堆分配(new方式),则可以通过deleteLater来进行释放)

(4)使用QObject::moveToThread(QThread*)方法,把QObject对象转移到新的线程中。

(5)把线程的finished()信号与QObject的deleteLater()类连接,这个是安全释放线程的关键,不连接的话,会导致内存泄漏。

(6)连接其他信号槽,如果是在连接信号槽之前调用moveToThread,不需要处理connect函数的第五个参数,这个参数是表示信号槽的连接方式;否则,如果在连接所有信号槽之后再调用moveToThread,则需要显式声明Qt::QueuedConnection来进行信号槽连接。

(7)完成一系列初始化后,调用QThread::start()来启动线程。

(8)在完成一系列业务逻辑后,调用QThread::quit()来退出线程的事件循环。使用继承QObject的方法来创建和启动线程,这种方法比使用QThread来创建线程要灵活得多。使用这种方法创建线程,整个线程对象都在新的线程中运行,而不像QThread那样,只有run()函数在新的线程中运行。QObject自身的信号槽和事件处理机制,可以灵活地应用到业务逻辑中。

下面,我们基于方法一(继承QThread)的例程,使用继承QObject的方法来创建线程。

1、先用Qt Creator构建一个工程,命名为:006_qthread_object,关于如何构建工程,请参考“第一个嵌入式QT应用程序”的具体内容。

2、双击打开“widget.ui”文件,构建界面,构建后的界面如下图所示:

界面描述:

[moveToThread run 1]:发送信号启动耗时任务1

[moveToThread run 2]:发送信号启动耗时任务2

[stop thread run]:修改变量,退出耗时任务的运行

[Clear Browser]:清空信息显示窗口

3、创建一个ThreadObject.h头文件,在头文件定义一个继承于QOBject的类ThreadObject,类的具体成员变量和成员函数,如下所示:

class ThreadObject : public QObject
{
    Q_OBJECT

signals:
    void message(const QString& info);  //通过此信号,发送需要打印的message消息
    void progress(int present);  //通过此信号,发送ProgressBar的进度百分比

public:
    ThreadObject(QObject* parent = NULL);
    ~ThreadObject();
    void setRunCount(int count);  //设置运行次数
    void stop();


public slots:
    void runSomeBigWork1();     //线程的while循环,在里面执行耗时任务
    void runSomeBigWork2();      //线程的while循环,在里面执行耗时任务

private:
    int m_runCount;       //while循环的运行次数
    int m_runCount2;      //while循环的运行次数
    bool m_isStop;        //线程停止标志位

    QMutex m_stopMutex;

    void msleep(unsigned int msec);  //线程休眠函数
};

4、创建一个ThreadObject.cpp文件,ThreadObject类里面的成员函数,均在这个文件里面实现。代码如下所示:

//线程的耗时操作都在这里进行
void ThreadObject::runSomeBigWork1()
{
    {
        QMutexLocker locker(&m_stopMutex);
        m_isStop = false;
    }

    int count = 0;
    QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
    emit message(str);
    int process = 0;
    emit progress(process);
    while(1)
    {
        {
            QMutexLocker locker(&m_stopMutex);
            if(m_isStop)    //通过m_isStop变量来退出线程
                return;
        }

        int pro = ((float)count / m_runCount) * 100;
        if(pro != process)
        {
            process = pro;
            emit progress(((float)count / m_runCount) * 100);   //线程运行的百分比
            emit message(QString("Object::run times:%1,m_runCount:%2").arg(count).arg(m_runCount));
        }
        msleep(1000);
        count++;

        if(m_runCount < count)   //线程达到最大的运行次数,则退出
        {
            break;
        }
    }
}

//线程的耗时操作都在这里进行
void ThreadObject::runSomeBigWork2()
{
    {
        QMutexLocker locker(&m_stopMutex);
        m_isStop = false;
    }

    int count = 0;
    QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
    emit message(str);
    int process = 0;
    QElapsedTimer timer;
    timer.start();
    while(1)
    {
        {
            QMutexLocker locker(&m_stopMutex);
            if(m_isStop)
                return;
        }

        int pro = ((float)count / m_runCount2) * 100;
        if(pro != process)
        {
            process = pro;
            emit progress(pro);
            emit message(QString("%1,%2,%3,%4").arg(count).arg(m_runCount2).arg(pro).arg(timer.elapsed()));
            timer.restart();
        }

        msleep(1000);
        count++;

        if(m_runCount2 < count)   //线程达到最大的运行次数,则退出
        {
            break;
        }
    }
}

线程的耗时操作都在这两个函数中进行,这两个函数都是通过ui线程中的信号进行触发的。同时,为了对线程进行停止,这里使用了一个m_isStop的成员变量。通过在ui线程中,调用stop()函数修改m_isStop变量,达到退出线程的目的。注意,修改m_isStop变量时,要进行互斥锁操作。

5、在widget.cpp文件中,ui窗口进行构造时,先设置进度条的初始状态,并关联定时器的超时槽函数,这个定时器用来监控ui线程是否卡死。代码如下所示:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    m_obj = NULL;
    m_objThread = NULL;

    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);
    ui->progressBar_heart->setRange(0,100);
    ui->progressBar_heart->setValue(0);

    //启动一个定时器,用来监视ui线程是否有卡死
    connect(&m_heart,SIGNAL(timeout()),this,SLOT(heartTimeOut()));
    m_heart.setInterval(100);

    m_heart.start();   //启动定时器,不断更新heartbeat进度条

    //打印出 ui 的线程id
    receiveMessage(QString("%1->%2,current ui id is:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
}

6、点击 [moveToThread run 1] 或 [moveToThread run 2] 按钮,即可调用startObjThread()启动线程,然后通过发送信号,唤起线程耗时操作的槽函数。startObjThread()函数的具体实现如下所示:

//使用moveToThread的方式启动一个线程
void Widget::startObjThread()
{
    if(m_objThread)   //如果这个线程已经存在,则不再启动
    {
        return;
    }
    m_objThread= new QThread();    //创建一个QThread对象
    m_obj = new ThreadObject();    //使用继承于QObject的类创建一个对象
    m_obj->moveToThread(m_objThread);    //把继承于QObject的类对象转移到新的线程运行

    //关联deleteLater槽函数,这是线程安全退出的关键
    connect(m_objThread,SIGNAL(finished()),m_objThread,SLOT(deleteLater()));
    connect(m_objThread,SIGNAL(finished()),m_obj,SLOT(deleteLater()));

    //关联两个信号,用于启动线程里面的耗时操作
    connect(this,SIGNAL(startObjThreadWork1()),m_obj,SLOT(runSomeBigWork1()));
    connect(this,SIGNAL(startObjThreadWork2()),m_obj,SLOT(runSomeBigWork2()));

    //关联两个信号,用于更新进度条和打印信息
    connect(m_obj,SIGNAL(progress(int)),this,SLOT(progress(int)));
    connect(m_obj,SIGNAL(message(QString)),this,SLOT(receiveMessage(QString)));

    m_objThread->start();
}

这里需要注意,QThread对象是通过new方法在堆内创建的,线程finished()退出的时候,需要关联deleteLater槽函数进行线程的安全退出和线程资源回收。否则,线程所占用的内存资源就不会进行释放,长时间下去,会造成内存资源泄漏。

7、至此,使用继承QObject这种方法来实现多线程已经介绍完毕,这种方法比使用继承QThread更加方便灵活,使用这种方法的总结如下:

(1)如果线程里面使用到消息循环,信号槽,事件队列等等操作,建议使用QObject来创建线程。

(2)继承QObject的类对象,都不能指定其父对象。

(3)新线程里面耗时操作,都定义为槽函数,方便通过信号启动线程。

(4)操作线程里面的成员变量时,为了安全,需要加锁后再进行操作。

(5)互斥锁QMutex会带来一点性能损耗。

8、把程序编译完后,下载到开发板运行,运行现象如下图所示:

实验现象说明:

(1)程序开始运行时,先打印出ui的线程ID。

(2)点击[moveToThread run 1]按钮,耗时任务work1开始运行,并打印出运行的线程ID。

(3)点击[moveToThread run 2]按钮,耗时任务work2开始运行,并打印出运行的线程ID。

(4)点击[stop thread run]按钮,耗时任务停止运行,并打印出stop()函数所在的线程ID。

(5)在work1运行期间,如果开启work2任务,则work1任务会中断,反之亦然。

(6)任务work1和任务work2均由同一个线程管理并运行。

(7)stop()函数与新建线程不在同一个线程内,stop()函数是归属于ui线程的。

 

点击这里,下载源码

 

                                                   欢迎关注公众号 【微联智控】

 

2019-04-16 14:03:31 wenjs0620 阅读数 1261

        本文的内容是拜读完以下文章后的总结,喝水不忘挖井人,感谢前辈的肩膀,让我们这些晚辈少走弯路,走得更远。如果已经理解了原作者的文章,则可完全忽略本文,感谢支持和关注。https://blog.csdn.net/czyt1988/article/details/64441443

        在嵌入式Linux应用程序的开发过程中,多线程永远是一个不可逃避的话题。多线程的出现,可以使一些任务处理更加方便快捷。使用多线程,可以把原来复杂庞大的系统任务,分解成多个独立的任务模块,方便开发人员管理,使程序架构更加紧凑,更加稳定。

        关于线程的简单通俗理解,请参考以下文章:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

在QT开发过程中,使用多线程,有两种方法:

方法一:继承QThread的 run() 函数,把复杂的循环逻辑放在run() 函数中执行。

方法二:把一个继承于QObject的类,使用moveToThread() 方法,转移到一个QThread的类对象中。

 

目标:了解QT如何分别使用两种方法,实现多线程编程。

功能:在i.MX6UL开发板上运行多线程实验,并把实验现象在显示屏上进行显示。

 

方法一:继承QThread类,重载run() 方法。

使用此方法进行QT多线程方法,有一条很重要!很重要!很重要!的规则需要记住:

继承QThread类,创建线程对象后,只有run()方法运行在新的线程里,类对象里面的其他方法都在创建QThread类的线程里运行。

简单地举一个例子:

如果在QT界面的ui线程里,使用继承了QThread的类去定义一个对象qthread,并且重载了run()函数,这个类还有其他函数。那么,调用对象qthread里面的非run()函数,这些函数就会在ui线程中执行,并不会产生新的线程ID。因此,如果要执行耗时的任务,最好把任务逻辑都写在run()函数中,否则,耗时的任务会把ui阻塞,导致ui出现卡死现象。还有一点要注意,如果要在非run()函数和run()函数里面,进行qthread对象里面的某一个变量修改,要注意进行加锁操作。因为,run()函数与非run()函数运行于不同的线程。

继承QThread类,重载run()方法,这样开启一个线程比较简单,但在开发过程中,我们更关注以下问题:

1、在ui线程中,调用了继承QThread类里面的方法,会不会造成ui卡顿。

2、在ui线程中,调用了QThread::quit() / QThread::exit()/ QThread::terminate() 会不会停止线程。

3、如何安全地退出一个线程?

4、如何正确地启动一个线程?

      > 如何正确地启动一个全局线程?

      > 如何正确地启动一个局部线程?

 

1、为了验证线程的相关问题,我们先编写一段简单的代码,使用QThread类进行线程创建。先用Qt Creator构建一个工程,命名为:005_qthread_test,关于如何构建工程,请参考“第一个嵌入式QT应用程序”的具体内容。

2、双击打开“widget.ui”文件,构建界面,构建后的界面如下图所示:

界面描述:

QThread run:点击此按钮,开始运行使用QThread类进行创建的线程,即运行run()函数。

QThread quit:点击此按钮,执行Qthread::quit()函数。

QThread Terminate:点击此按钮,以安全的方法退出线程。

QThread exit:点击此按钮,执行QThread::exit()函数。

QThread run local:点击此按钮,开始运行一个局部线程。

Clear Browser:清空显示区域的内容。

get something:点击此按钮,在ui线程中调用QThread类里面的函数,观察其线程id

set something:点击此按钮,在ui线程中调用QThread类里面的函数,观察其线程id

do something:点击此按钮,在ui线程中调用QThread类里面的函数,观察其线程id

heartbeat进度条:在ui线程中运行,观察ui线程是否有卡死现象。

thread进度条:在QThread线程中运行,显示线程运行的百分比。

信息窗口:显示程序运行时,各个线程的打印信息。

3、针对以上提出的问题,首先,我们先把已经编译下载好的程序,在开发板中运行起来,看一下实验现象。

点击这里,查看实验现象

实验现象说明:

  1. 点击 [QThread run] 按钮,ThreadFormQThread类(继承于QThread类)里面的run() 函数开始运行。run()函数首先打印线程启动信息,打印当前的线程ID,每隔1秒钟,更新一次thread进度条,打印已运行的次数,打印线程的具体信息。
  2. 点击 [QThread quit] 和 [QThread exit],调用QThread::quit() 和 QThread::exit() 函数,线程并没有停止运行,因此,以上两个函数并不能结束线程的运行。
  3. 点击 [get something] [set something] [do something] 按钮,打印出调用以上三个函数的线程ID,是ui线程ID。这就说明了,在ui线程里调用ThreadFormQThread类对象里面的函数,函数也是运行在ui线程,而非新创建的线程,只有ThreadFormQThread类对象里面的run()函数才以新的线程来运行。
  4. 点击 [QThread Terminate] 按钮,安全退出线程,即安全退出run()函数。
  5. 点击 [Clear Browser] 按钮,清除显示框的内容。
  6. 点击 [QThread run local] 按钮,启动一个新的局部线程,这个线程的父线程不是ui线程,而且,这个线程执行完后,会自动销毁线程运行时的所有资源。

 4、新建一个 ThreadFormQThread.h 文件,创建一个继承QThread的类。

class ThreadFromQThread : public QThread
{
    Q_OBJECT

signals:
    void message(const QString& info);  //通过此信号,发送需要打印的message消息
    void progress(int present);  //通过此信号,发送ProgressBar的进度百分比

public slots:
    void stopImmediately();   //调用此槽函数,结束进程

public:
    ThreadFromQThread(QObject* par);
    ~ThreadFromQThread();
    void setSomething();   //发送message信号,打印某些信息
    void getSomething();   //发送message信号,打印某些信息
    void setRunCount(int count);  //设置run()函数的循环次数
    void run();           //重载run()函数
    void doSomething();   //循环打印某些信息

private:
    int m_runCount;
    QMutex m_lock;
    bool m_isCanRun;
};

 

5、新建一个 ThreadFormQThread.cpp文件,编写类方法的具体实现(详细内容请参见源码),重载run()函数。以下是run()函数的具体实现。

//线程处理任务的函数体,这个run函数会在一个新的线程里运行
void ThreadFromQThread::run()
{
    int count = 0;
    QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
    emit message(str);

    m_isCanRun = true;

    while(1)
    {
        sleep(1);
        ++count;
        emit progress(((float)count / m_runCount) * 100); //发送进度条百分比
        emit message(QString("ThreadFromQThread::run times:%1").arg(count));  //打印已运行的次数
        doSomething();  //打印线程的具体信息
        if(m_runCount == count)   //如果等于线程最大的运行次数,则退出
        {
            break;
        }

        //在下面的函数体内,安全退出线程
        {
            QMutexLocker locker(&m_lock);
            if(!m_isCanRun)//在每次循环判断是否可以运行,如果不行就退出循环
            {
                return;
            }
        }
    }
}

重载run()函数的实现内容,当调用QThread::start()后,这个run()函数就开始进行在新的线程里被调用,刚进入函数时,打印出当前调用run()函数的线程ID,可以看出,跟ui线程是不一样的ID。把m_isCanRun变量设置为true,这个变量用来安全退出线程的,这个变量只能在m_lock这个互斥锁里面被修改。

 

6、开始回答以上提出的问题,在ui线程中,调用了继承QThread类里面的方法,会不会造成ui卡顿?

void Widget::onButtonQthread1SetSomethingClicked()
{
    m_thread->setSomething();//在ui线程中,调用这个函数,这个函数打印来的线程ID是ui的线程ID
}

void Widget::onButtonQthread1GetSomethingClicked()
{
    m_thread->getSomething();//在ui线程中,调用这个函数,这个函数打印来的线程ID是ui的线程ID
}

void Widget::onButtonQThreadDoSomthingClicked()
{
    m_thread->doSomething();  //在ui线程中,调用这个函数,这个函数打印来的线程ID是ui的线程ID
}

如代码所示,在ui线程中,点击按钮,分别通过m_thread对象(注意:m_thread对象是在ui线程中生成的)直接调用里面的函数,里面的函数也是归属于ui线程的,从实验现象可以看出,heartbeat进度条一直在更新,验证了在ui线程中,调用了继承QThread类里面的方法,并不会造成ui卡顿。

7、在ui线程中,调用了QThread::quit() / QThread::exit()/ QThread::terminate() 会不会停止线程?

void Widget::onButtonQthreadQuitClicked()
{
    ui->textBrowser->append("m_thread->quit() but not work");
    m_thread->quit();
}

void Widget::onButtonQthreadTerminateClicked()
{
    //m_thread->terminate();   //调用这个函数,强制退出线程,不建议使用
    m_thread->stopImmediately(); //调用这个函数,安全退出线程
}

void Widget::onButtonQThreadExitClicked()
{
    m_thread->exit();
}

如代码所示,分别调用QThread::quit() / QThread::exit() / QThread::terminate() 进行退出线程。从实验现象可以得出,QThread::quit() 和QThread::exit() 这两个函数,并不会让线程退出,因为这两个函数只对QThread::exec()有效。QThread::terminate()则会强制退出线程,不管线程的运行情况(不建议使用这种方法)。应该使用stopImmediately()函数,安全退出线程。stopImmediately()函数的内容如下:

void ThreadFromQThread::stopImmediately()
{
    {
        QMutexLocker locker(&m_lock);
        m_isCanRun = false;
    }
}

可以看出,在m_lock互斥锁的保护下,把m_isCanRun变量置为false,当run()函数的while循环遇到这个变量为false,则break当前运行的循环,结束线程。

8、如何安全地退出一个线程?

如第7点描述所示,要安全地退出一个线程,可以在外部使用stopImmediately()函数。因为是在ui主线程中调用这个函数的,并使用了互斥锁进行保护,因此,当这个函数被调用时,会马上把m_isCanRun变量置为false,这样,即可安全地退出run()函数的while循环,run()函数在返回的时候,即被视为线程结束,会发射finish()信号,槽函数onQThreadFinished()即会被调用。

//线程结束后,在窗口打印信息
void Widget::onQThreadFinished()
{
    ui->textBrowser->append("ThreadFromQThread finish");
}

9、如何正确地启动一个线程?(全局线程和局部线程)

线程的启动有多种方法,这几种方法都涉及到线程由谁(父线程)去生成,以及线程如何安全地退出。关于线程的生成和退出,首先需要搞清楚的是线程的生命周期,这个线程的生命周期是否跟ui线程一致(全局线程),还是线程只是临时生成,完成任务后就进行销毁(局部线程)。

全局线程会在创建时,把ui线程作为自己的父对象,当ui线程析构时,全局线程也会进行销毁。但此时,应该关注一个问题:当ui线程结束(窗体关闭)时,全局线程还没有结束,应当如何处理?如果没有处理好这种情况,在ui线程析构时,强行退出全局线程,会导致程序崩溃。往往这种线程的生命周期是伴随着ui线程一起开始与结束的。

局部线程,也叫临时线程,这种线程一般是要进行一些耗时任务,为了防止ui线程卡死而存在的。同样地,我们更关注以下问题:在局部线程运行期间,如果因为某些因素要停止线程,该如何安全地退出局部线程?例如,在图片打开期间(还没有完全打开),要切换图片,该如何处理。在音乐播放期间,要切换下一首音乐,应如何处理。

 

如何正确地启动一个全局线程?

由于是全局线程,因此,在ui窗体构建的时候,线程随即被构建,并且把ui窗体设置为线程的父对象。此时,需要注意的是,不能随便delete线程指针!!!因为这个线程是伴随着ui线程构建的,存在于QT的循环事件队列中,如果手动delete了线程指针,程序会很容易崩溃。正确的退出方法,可以使用 void QObject::deleteLater() [SLOT] 这个槽函数。全局线程的创建代码,如下图所示:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);
    ui->progressBar_heart->setRange(0,100);
    ui->progressBar_heart->setValue(0);

    connect(ui->pushButton_qthread1,SIGNAL(clicked()),this,SLOT(onButtonQThreadClicked()));
    connect(ui->pushButton_qthread1_setSomething,SIGNAL(clicked()),this,SLOT(onButtonQthread1SetSomethingClicked()));
    connect(ui->pushButton_qthread1_getSomething,SIGNAL(clicked()),this,SLOT(onButtonQthread1GetSomethingClicked()));
    connect(ui->pushButton_qthreadQuit,SIGNAL(clicked()),this,SLOT(onButtonQthreadQuitClicked()));
    connect(ui->pushButton_qthreadTerminate,SIGNAL(clicked()),this,SLOT(onButtonQthreadTerminateClicked()));
    connect(ui->pushButton_qthreadExit,SIGNAL(clicked()),this,SLOT(onButtonQThreadExitClicked()));
    connect(ui->pushButton_doSomthing,SIGNAL(clicked()),this,SLOT(onButtonQThreadDoSomthingClicked()));
    connect(ui->pushButton_clear_broswer,SIGNAL(clicked()),this,SLOT(onButtonClearBroswerClicked()));
    connect(ui->pushButton_qthreadRunLocal,SIGNAL(clicked()),this,SLOT(onButtonQThreadRunLoaclClicked()));
    //connect(ui->pushButton_qobjectStart,&QPushButton::clicked,this,&Widget::onButtonObjectMove2ThreadClicked);
    //connect(ui->pushButton_objQuit,&QPushButton::clicked,this,&Widget::onButtonObjectQuitClicked);

    connect(&m_heart,SIGNAL(timeout()),this,SLOT(heartTimeOut()));
    m_heart.setInterval(100);

    m_thread = new ThreadFromQThread(this);//在这里创建了一个全局线程
    connect(m_thread,SIGNAL(message(QString)),this,SLOT(receiveMessage(QString)));  //接收message信号,在窗口打印线程信息
    connect(m_thread,SIGNAL(progress(int)),this,SLOT(progress(int)));//接收progress信号,更新进度条
    connect(m_thread,SIGNAL(finished()),this,SLOT(onQThreadFinished()));//接收线程结束信号,打印线程结束的消息

    m_heart.start();   //启动定时器,不断更新heartbeat进度条

    m_currentRunLoaclThread = NULL;
}

在ui窗体构建时,创建一个全局线程对象,并关联槽函数,此时,线程对象已经构建,但线程还没有运行,run()函数还没有执行。注意,这里没有使用void QObject::deleteLater() [SLOT] 这个槽函数,而是使用了另一种方法来进行线程结束。void QObject::deleteLater() [SLOT] 这个槽函数会在局部线程那里进行使用。

10、要启动线程,点击界面上的 [QThread run] 按钮,调用onButtonQThreadClicked() 槽函数,这个函数里面,判断全局线程是否已经运行,如果没有运行,则调用QThread::start()函数,启动线程(即run()函数开始运行)。

void Widget::onButtonQThreadClicked()
{
    ui->progressBar->setValue(0);
    if(m_thread->isRunning())  //判断线程是否已经在运行
    {
        return;
    }
    m_thread->start();   //启动线程,执行线程的run()函数
}

如果在线程运行期间,重复调用QThread::start(),其实是不会进行任何处理的。在按钮的槽函数中,也进行了适当的判断。

11、启动运行一个全局线程,是很简单的,但我们更应该关注如何安全退出一个全局线程,因为这个全局线程是在ui线程中进行生成的,因此,在ui窗口析构时,应该需要判断线程是否已经运行结束(或者主动安全结束线程),才能进行 delete ui 操作。

Widget::~Widget()
{
    qDebug() << "start destroy widget";
    m_thread->stopImmediately();//由于此线程的父对象是Widget,因此退出时需要进行判断
    m_thread->wait();   //在这里,会阻塞等待线程执行完
    delete ui;
    qDebug() << "end destroy widget";
}

在ui线程析构时,调用 stopImmediately() 安全退出线程,然后调用 QThread::wait() 等待线程结束,QThread::wait()会一直阻塞,这样才不会导致线程还没有结束就 delete ui,造成程序崩溃。

如何正确地启动一个局部线程?

12、启动一个局部线程(运行完自动销毁资源的线程),操作方法跟启动一个全局线程差不多,主要是需要多关联一个槽函数:void QObject::deleteLater() [SLOT],这个函数是局部线程安全退出的关键函数。点击 [QThread run local] 按钮,调用onButtonQThreadRunLoaclClicked()函数,启动一个局部线程。

//这个函数用来创建局部线程,所谓局部线程,就是运行完后,自动销毁的线程
void Widget::onButtonQThreadRunLoaclClicked()
{
    //判断这个局部线程是否已经存在,如果已经存在,则先退出
    if(m_currentRunLoaclThread)
    {
         m_currentRunLoaclThread->stopImmediately();
    }

    ThreadFromQThread* thread = new ThreadFromQThread(NULL);//这里父对象指定为NULL
    connect(thread,SIGNAL(message(QString)),this,SLOT(receiveMessage(QString)));//接收message信号,在窗口打印线程信息
    connect(thread,SIGNAL(progress(int)),this,SLOT(progress(int)));//接收progress信号,更新进度条
    connect(thread,SIGNAL(finished()),this,SLOT(onQThreadFinished()));//接收线程结束信号,打印线程结束的消息
    connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater()));//线程结束后调用deleteLater来销毁分配的内存
    //线程销毁时,会发送destroyed信号,然后把临时变量再次赋值为NULL
    connect(thread,SIGNAL(destroyed(QObject*)),this,SLOT(onLocalThreadDestroy(QObject*)));
    thread->start();//启动线程,执行run()函数
    m_currentRunLoaclThread = thread;  //保存当前正在运行的线程

}

与全局线程不同的是,局部线程在new ThreadFromQThread(NULL)时,并没有给它指定父对象,deleteLater()槽函数与线程的finish()信号进行绑定,线程结束时,自动销毁线程创建时分配的内存资源。对于局部线程,还需要注意重复调用线程的情况。对于比较常见的需求,是在局部线程还没有执行完的时候,需要重新启动下一个线程。这时,就需要安全结束本次局部线程,再重新创建一个新的局部线程。例如:在一张图片还没有加载完成的时候,切换到下一张图片;在一首歌曲还没有播放完成的时候,切换到下一首歌曲。针对这种情况,我们使用了一个成员变量m_currentRunLoaclThread来记录当前局部线程的运行情况,当m_currentRunLoaclThread变量存在时,先结束线程,然后再生成新的局部线程。

13、除了使用成员变量来记录当前运行的局部线程,还需要关联destroy(QObject*)信号,这个信号用于当前局部线程销毁时,重新把m_currentRunLoaclThread变量置为NULL。

//局部线程销毁函数,
void Widget::onLocalThreadDestroy(QObject *obj)
{
    if(qobject_cast<QObject*>(m_currentRunLoaclThread) == obj)
    {
        m_currentRunLoaclThread = NULL;
    }
}

也可以在这个onLocalThreadDestroy(QObject *obj)的槽函数中,进行局部线程的资源回收工作。

14、至此,使用继承QThread类,重载run()方法来创建线程,已经介绍完毕,以下是这种方法的简单总结。

        a.继承QThread类,只有run()方法是运行在新的线程里,其他方法是运行在父线程里。

        b.执行QThread::start()后,再次执行该函数,不会再重新启动线程。

        c.在线程run()函数运行期间,执行QThread::quit()和QThread::exit(),不会导致线程退出。

        d.使用成员变量和互斥锁,可以进行线程的安全退出。

        e.对于全局线程,不能delete线程指针,在ui窗体析构时,应使用QThread::wait()等待全局线程执行完毕,再进行delete ui

        f.对于局部线程,要善于使用QObject::deleteLater()和QObject::destroy()来销毁线程。

 

点击这里,下载源码

 

                                                   欢迎关注公众号 【微联智控】

 

没有更多推荐了,返回首页