2009-10-06 15:42:00 xgjianstart 阅读数 5055
  • 嵌入式Linux多任务编程

    本课程主要讲解Linux环境进程与线程的概述,进程创建,进程间通信编程,多线程编程。 学习条件: 1.C编程基础 2.Linux编程基础

    8628 人正在学习 去看看 沈寒

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)是一个著名的同步问题。
2019-04-16 14:03:31 wenjs0620 阅读数 1368
  • 嵌入式Linux多任务编程

    本课程主要讲解Linux环境进程与线程的概述,进程创建,进程间通信编程,多线程编程。 学习条件: 1.C编程基础 2.Linux编程基础

    8628 人正在学习 去看看 沈寒

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

 

点击这里,下载源码

 

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

 

2019-08-21 16:13:50 Z_Silence 阅读数 617
  • 嵌入式Linux多任务编程

    本课程主要讲解Linux环境进程与线程的概述,进程创建,进程间通信编程,多线程编程。 学习条件: 1.C编程基础 2.Linux编程基础

    8628 人正在学习 去看看 沈寒

《从实践中学嵌入式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

2014-01-02 13:39:26 ce123 阅读数 4132
  • 嵌入式Linux多任务编程

    本课程主要讲解Linux环境进程与线程的概述,进程创建,进程间通信编程,多线程编程。 学习条件: 1.C编程基础 2.Linux编程基础

    8628 人正在学习 去看看 沈寒

嵌入式linux的网络编程(6)--多线程文件服务器

1.简介

本文介绍一个多线程文件服务器的例子,服务器根据客户端的请求,将其所要求的文件发给客户端.采用多线程工作,可以同时为多个客户端提供服务.这样的需求情景在很多实际应用中都是存在.

整个服务器端程序由四个文件构成,分别为:fileserver.c,fileclient.c,rw.h和rw.c.其中fileserver.c为多线程服务器的主程序,fileclient.c是测试用的客户端程序,rw.h和rw.c主要提供了大数据量收发的功能函数.

2.文件服务器的主程序

在之前的文章中,我们曾讨论过多线程程序设计的问题,而且还与多进程的程序做过对比,得出在嵌入式系统中,应该优先考虑使用多线程来实现多任务处理.虽然采取创建子进程的方式来实现服务器同时对多个客户端服务的做法是最为常见的,但考虑嵌入式系统的实际,我们还是讨论多线程的实现方法.其实基本原理是一样的,就是为每个与客户端的连接创建一个处理子程序.

下面我们列出服务器主程序的代码.

/**************************************************************************************/
/*文件:fileserver.c											 */
/*简介:多线程文件服务器主程序。								 */
/*************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "rw.h"
#define BACKLOG 10

/*工作线程函数*/
void *deal_request(void * arg)
{
	int connfd = *((int *)arg);
	char file_name[MAX_LEN];

	/*分离模式,线程工作完毕就清理资源退出*/
	pthread_detach(pthread_self());

	/*处理客户端请求*/
	if(read_cmd(connfd,file_name,sizeof(file_name))==-1)
	{
		printf("read file name error!\n");
	}
	else
	{
		/*发送请求的文件数据*/
		if(send_file(connfd,file_name)==-1)
		{
			printf("Send file error\n");
		}
	}

	/*对应于主程序中的malloc()函数*/
	free(arg);
	close(connfd);
	pthread_exit(NULL);
}
int main(int argc, char* argv[])
{
	int sockfd;
	int *connfd;
	struct sockaddr_in servaddr;
	struct sockaddr_in cliaddr;
	struct sockaddr_in tempaddr;
	socklen_t templen;
	socklen_t clilen;

	/*建立TCP套接字*/
	if((sockfd = socket(AF_INET, SOCK_STREAM,0)) == -1)
	{
		perror("socket");
		exit(1);
	} 

	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = 0; /*随机的端口号*/
	/*绑定TCP套接字*/
	if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) == -1)
	{
		perror("bind");
		exit(1);
	}

	templen = sizeof(struct sockaddr);
	/*获得套接字的本地地址*/
	if (getsockname(sockfd, (struct sockaddr *)&tempaddr,&templen) == -1)
	{
		perror("getsockname");
		exit(1);
	}
	printf("Server is listening on port %d\n", ntohs(tempaddr.sin_port));

	/*开始监听端口*/
	if (listen(sockfd,BACKLOG) == -1)
	{
		perror("listen");
		exit(1);
	}	

	for(;;)
	{
		pthread_t th;
		clilen = sizeof(cliaddr);
		/*通信套接字,一定需要动态分配,以区别不同的线程*/
		connfd = (int *)malloc(sizeof(int));
		*connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&clilen);

		if(*connfd == -1)
		{
			perror("accept");
			continue;
		}

		/*为每一个与客户端的连接建立一个线程来处理其请求*/
		if(pthread_create(&th,NULL,deal_request,connfd)!=0)
		{
			perror("pthread_create");
			break;
		}   
	}
	return 0;
}
接下来我们对这个程序进行详细的分析

3.动态分析监听端口

出于某种需求,服务器的监听端口需要动态的分配,getsockname()函数可以实现该功能.getsockname()函数的作用是获得指定套接字的本地地址,其原型为:

#include <sys/socket.h>
int getsockname (int sockfd, struct sockaddr *addr, int *addrlen);
其参数的含义如下:

  • sockfd:当前套接字的标示符;
  • addr:指向一个套接字地址结构,返回的本地地址信息保存在这个结构体中;
  • addrlen:表示addr所占的内存大小.

在绑定一个通配IP地址(INADDR_ANY)的TCP服务器上,通过调用该函数可以获得本地的IP地址和端口等地址信息.在上面的程序中,由于服务器的监听端口是随机的(为0),所以为了让客户端知道服务器的端口号,因此使用该函数以获得实际分配的端口号,这样就实现了动态分配监听端口的功能了.

4.多线程服务器的实现

现在我们具体来分析这段代码.实际上大部分的代码都与前面博客中的例子是相似的,一直到for循环的代码才有了比较明显的区别.所以我们主要分析for循环和线程处理函数的内容.为了便于叙述,这里将for循环单独列出,并标上行号:


在这个for循环中,将要完成的任务是:建立连接套接字描述符,然后新建一个数据处理子线程,并将这个描述符作为参数传递给数据处理子线程.这个过程看似普通,但有一个很容易犯错的地方,请看93和103行的注释,这涉及到线程对贡献数据的访问.

accept函数我们已经比较熟悉了,参数connfd是传递给数据处理线程的函数参数,connfd变量保存着连接套接字描述符.如果connfd不是动态分配的,而是在main函数定义的变量,则每个子线程的参数connfd都指向了同一个内存地址,由于多线程环境下,所有的线程都和创建它们的进程位于同一个内存地址空间中,如果在前一个线程还没有处理完数据传输的情况下,新的连接建立了,这时connfd的值将修改为新的连接的套接字描述符,原来那个还没有处理完的套接字消失了,结果必然导致数据传输的失败.

因此,为每一个连接的套接字描述符新建一块内存来保存其内容,知道数据处理完后再将这块内存释放,就可以避免套接字描述符丢失的问题了.

为了方便处理线程退出的情况,我们设置了线程的分离属性:当线程中止运行后就自动清理其所占的资源并退出.具体的实现可以看看前面给出的源码.

5.大量数据的读写函数

这里所指的大数据量是指超过read()或write()一次操作所能承载的数据量.我们知道read()函数和write()函数都有一个特点,若指定的数据量较大时,往往不能通过一次读写操作就完成所有的数据I/O工作,往往需要根据它们返回的实际读取或写入的数据量,再进行若干此的读写操作.在rw.c文件中提供了readall()和writeall()两个函数,解决了这个问题.代码如下:

/**************************************************************************************/
/*文件:rw.h												 */
/*简介:自定义I/O函数头文件。								 */
/*************************************************************************************/
#ifndef  __RW_H
#define  __RW_H
/*读写缓冲区的大小*/
#define MAX_LEN 1024*10 /*10KB*/

ssize_t readall(int fd, void* buf,size_t *len);
ssize_t writeall(int fd, void* buf,size_t *len);
int read_cmd(int sockfd,char* cmd_buf,int len);
int send_file(int sockfd,char *file_name);
#endif
接下来是这些函数的实现:

/**************************************************************************************/
/*文件:rw.c												 */
/*简介:自定义I/O函数实现文件。								 */
/*************************************************************************************/
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include "rw.h"

/*read()函数的扩展,解决读不全的问题*/
ssize_t readall(int fd, void* buf,size_t *len)
{
	size_t nleft;
	ssize_t nread;
	ssize_t total;
	char *ptr;
	ptr = buf;
	nleft = *len;
	total = 0;

	/*反复读,直到没有新的数据可读*/
	while( nleft > 0)
	{
		if((nread = read(fd,ptr,*len)) == -1)
		{
			perror("readall");
			break;
		}
		if(nread ==0)
			break;
		nleft -= nread;
		ptr += nread;
		total += nread;
		*len = nleft;
	} 
	*len = total;
	return (nread==-1)?-1:0;
}

/*write()函数的扩展,解决写不够的问题*/
ssize_t writeall(int fd, void* buf,size_t *len)
{
	size_t nleft;
	ssize_t nwrite;
	ssize_t total;
	const char *ptr;
	ptr = buf;
	nleft = *len;
	total = 0;

	/*反复写,直到所有的数据都写入*/
	while( nleft > 0)
	{
		if((nwrite = write(fd,ptr,*len))== -1)
		{
			perror("write all");
			break;
		}
		nleft -= nwrite;
		ptr += nwrite;
		total += nwrite;
		*len = nleft;
	} 
	*len = total;
	return (nwrite==-1)?-1:0;
}

/*分析客户端的命令,提取出请求的文件名*/
int read_cmd(int sockfd,char* cmd_buf,int len)
{
	char line[MAX_LEN];
	int my_len = 0;
	int total_len =0;
	char *ptr;
	int can_read;
	if(len > MAX_LEN)
		len = MAX_LEN;

	can_read = 1;
	strcpy(cmd_buf,"\0");

	while(can_read)
	{
		if((my_len = read(sockfd, line,len))<0 )
		{
			perror("read");
			return -1;
		}

		total_len += my_len;
		if(total_len > len)
		{
			printf("Recieve command error!\n");
			return -1;
		}

		if((ptr=strstr(line,"\r\n"))==NULL)
		{
			if(total_len <= len)
				strcat(cmd_buf, line); 
		}
		else
		{
			strncat(cmd_buf,line,ptr-line);
			can_read = 0;
		}
		printf("Client requests file: %s\n",cmd_buf);   
	}
	return 0;
}

/*发送文件至客户端*/
int send_file(int sockfd,char *file_name)
{
	int file_fd;
	int file_size;
	int read_left;
	int len;
	int error_flag;
	int readlen;
	struct stat file_state;
	char buffer[MAX_LEN];
	int dot_number = 0;

	if((file_fd = open(file_name,O_RDONLY)) == -1)
	{
		perror("open");
		return -1;
	}

	if(fstat(file_fd, &file_state)==-1)
	{
		perror("fstat");
		return -1;
	}
	file_size = file_state.st_size;
	read_left = file_size;
	len = MAX_LEN;
	while(read_left > 0)
	{
		/* now read the file */
		readlen = MAX_LEN;
		error_flag = readall(file_fd,buffer,&readlen);
		if(error_flag<0 )
		{
			return -1;
		}
		read_left -= readlen;
		len = readlen;

		error_flag = writeall(sockfd,buffer,&len);
		if(error_flag == -1)
			return -1;
		if(readlen==0 && read_left!=0)
		{
			printf("the file is not read fully!\n");   
			return -1;         
		}
		if(read_left==0)
		{
			printf("\nServer sent file over!\n");                  
		}
	}    
	close(file_fd);
	return 0;
}
为了解决read和write函数不能一次将要求的数据读出或者写入,read_all和write_all函数都采用了反复读或者写的方法,根据read或者write函数的返回值,判断还需要读或者写的数据量,并计算出偏移量,接着前一次的读或者写位置继续执行.

read_cmd和send_file函数是这个文件服务器实现其服务功能的主要函数.这些函数都比较常用,适用于很多应用场合,大家可以仔细分析一下.

6.客户端测试程序

有了上面的三个文件(http://download.csdn.net/detail/ce123/6764657),就可以构造出这个多线程的文件服务器了.同时,为了对这个服务器的功能进行测试,还需要编写一个合适的客户端程序,这样就有了下面的这个测试用的客户端程序.这个客户端程序是通过TCP协议与服务器端进行通信的,鉴于前面对TCP Client已做了详细的分析,这里就只列出该测试程序的代码,大家自己分析一下.

/*文件:fileclient.c											 */
/*简介:客户端测试程序。									 */
/*************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "rw.h"
/*换行计数*/
#define DOT_PERIOD 50

int main(int argc, char *argv[])
{
	int sockfd;
	int conn_ret;
	struct sockaddr_in servaddr;
	char cmd_buf[MAX_LEN];
	char recvbuf[MAX_LEN];
	int error_flag;
	int len = MAX_LEN;
	int file_fd;
	int dot_number;
	int total_len = 0;

	/*参数检查*/
	if(argc != 5)
	{
		printf("Usage: fileclient <address> <port> <src file> <des file>\n");
		return 0;
	}
	
	/*建立socket*/
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
	{
		perror("sock");
		exit(1);
	}

	/*连接服务器*/
	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(atoi(argv[2]));
	inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
	conn_ret = connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	if(conn_ret == -1)
	{
		perror("connect");
	}

	/*创建接收的文件*/
	file_fd = open(argv[4],O_CREAT |  O_WRONLY);
	if(file_fd == -1)
	{
		perror("open");
		exit(1);
	}

	/*构造客户端的请求消息,格式为“文件名\r\n”*/
	len = strlen(argv[3])+3;
	snprintf(cmd_buf,len,"%s\r\n",argv[3]);

	/*发送请求*/
	if((error_flag = writeall(sockfd,cmd_buf,&len))==-1)
	{
		exit(1);
	}

	/*缓冲的大小,决定了每次的I/O操作的数据量*/
	len = MAX_LEN;  	
	printf("\nfile is transferring:\n");
	while ((error_flag = readall(sockfd,recvbuf,&len))==0)
	{
		if(len == 0)
		{
			printf("\nClient has received file!\n");
			break;
		} 

		/*显示本次接收到的数据量*/
		printf(".");
		printf("read length is %d\n",len);
		dot_number++;
		if((dot_number % DOT_PERIOD) ==0)
		{
			printf("\n");
			dot_number = 0;
		}
		total_len+=len;

		/*将接收到的数据写入文件*/
		if(writeall(file_fd,recvbuf,&len) == -1)
		{
			printf("\nclient has some error when receive the file!\n");
			break;
		} 
		len  = MAX_LEN;
	}

	printf("\nRecevied %d bytes\n",total_len);
	close(file_fd);
	close(sockfd);
	return 0;
}
这个客户端的功能是从命令行得到所需要获取的文件的名称后,连接服务器并发送文件请求,服务器响应这个请求后将文件的数据发给客户端,然后写入这个文件中,最后断开连接并退出,每次只能请求和接收一个文件.

7.编译和测试结果




2018-08-20 21:28:04 qq_41998576 阅读数 49
  • 嵌入式Linux多任务编程

    本课程主要讲解Linux环境进程与线程的概述,进程创建,进程间通信编程,多线程编程。 学习条件: 1.C编程基础 2.Linux编程基础

    8628 人正在学习 去看看 沈寒

一、线程与进程

1.进程:是一个具有一定独立功能的程序的一次运行活动,进程是程序执行时的一个实例,同时也是资源分配的最小单元

   Linux中的进程包含:(1)“数据段”存放的是全局变量、常数以及动态数据分配的数据空间;

                   (2)“代码段”存放的是程序代码的数据。

                   (3)“堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量等。 

2.线程:是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

              多任务,并发的工作方式。

3.进程与线程的关系:

         一个进程由几个线程组成,线程与同属一个进程的其他的线程共享进程所拥有的全部资源。

    进程(资源分配的最小单位)             线程(程序执行的最小单位)
    进程切换占用资源,效率差                       通信效率高
      有独立的地址空间,健壮

                 没有独立的地址空间

(同一进程内的线程共享进程的地址空间)

4.使用线程的优点:(1)“节俭” ,无需分配地址空间;运行于一个进程中的多个线程,它们之间使用相同的地址空间

                                (2)线程间彼此切换所需的时间远远小于进程间切换所需要的时间

                                (3)线程间方便的通信机制,一个线程的数据可以直接为其它线程所用

                                (4)使多CPU系统更加有效

                                       (操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上) 

                               (5)改善程序结构

                                     (一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改)

二、多线程程序设计

(1)创建

      int pthread_create(pthread_t * tidp, const pthread_attr_t*attr, void*(*start_rtn)(void), void*arg)

           tidp:线程id

           attr: 线程属性(通常为空)

           start_rtn:线程要执行的函数

           arg:start_rtn的参数

(2)编译:因为pthread的库不是linux系统的库,所以在进行编译的时候要加上-lpthread

(3) 终止(进程中任何一个线程中调用exit或_exit,那么整个进程都会终止。)

          线程的正常退出方式有:(1) 线程从启动例程中返回

                                         (2) 线程可以被另一个线程终止

                                         (3) 线程自己调用pthread_exit函数

(4) 退出void pthread_exit(void * rval_ptr)

(5)等待: 阻塞调用线程,直到指定的线程终止。

       int pthread_join(pthread_t tid,void **rval_ptr)

              tid :等待退出的线程id

              rval_ptr:线程退出的返回值的指针

(6)线程同步: 

       线程之间对资源的竞争:

                (1)互斥量Mutex:线程在取出头节点前必须要等待互斥量,如果此时有其他线程已经获得该互斥量,那么该线程                                                    将会阻塞在这里.只有等到其他线程释放掉该互斥量后,该线程才有可能得到该互斥量。

                                                  互斥量从本质上说就是一把锁, 提供对共享资源的保护访问。

                (2) 信号灯Semaphore:

                (3)条件变量Conditions:

 

 

 

 

 

 

 

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