多线程应用程序设计

  • 格式:doc
  • 大小:196.00 KB
  • 文档页数:7

下载文档原格式

  / 12
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

多线程应用程序设计

摘要:嵌入式操作系统是一种支持嵌入式系统应用的操作系统软件,它是嵌入系统极为重要的组成部分。嵌入式操作系统具有能够有效管理越来越复杂的系统资源,能够把硬件虚拟化,能够提供库函数、驱动程序及工具集等特点。Linux是最常见的嵌入式操作系统。目前Linux 已广泛应用于信息家电、数据网络、工业控制、医疗卫生航空航天等众多领域。在本文中主要介绍多线程应用程序设计,通过编写经典的“生产者消费者”问题的实验,可以进一步熟悉Linux中的多线程编程,并且掌握用信号量处理线程间的同步和互斥问题。

Linux多线程编程更好的熟悉在Linux下进行编程的方法,熟悉ARM开发板的使用和开发环境的设置以及熟悉几个重要的PTHREAD 库函数的使用,掌握共享锁和信号量的使用方法。

关键词:嵌入式、多线程编程、互斥锁、条件变量

一、课题介绍

在嵌入式系统中,进程是程序执行和资源分配的基本单位。每个进程都拥有自己的数据段、代码段和堆栈段,这就造成了进程在进行切换等操作时都需要有比较复杂的上下文切换等动作。为了进一步减少处理机的空转时间,支持多处理器以及减少上下文切换开销,进程在演化中出现了另一个概念——线程。它是进程内独立的一条运行路线,处理器调度的最小单元,也可以称为轻量级进程。线程可以对进程的内存空间和资源进行访问,并与同一进程中的其他线程共享。因此,线程的上下文切换的开销比创建进程小很多。

同进程一样,线程也将相关的执行状态和存储变量放在线程控制表内。一个进程可以有多个线程,也就是有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间。在多线程系统中,进程与进程的关系如图1.1所示。

进程

用户地址空间

线程一线程二线程三图1.1 进程与线程关系

二、课题原理

(一)线程与多线程

为什么有了进程的概念后,还要再引入线程呢?理由一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右。

理由二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程却不一样,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

那么使用多线程有什么的优点呢?多线程程序作为一种多任务、并发的工作方式,优点有:(1)提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作置于一个新的线程,可以避免这种情况。(2)使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。(3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

(二)线程库函数

Linux系统下的多线程遵循POSIX(Portable Operating System Interface of Unix /可移植性操作系统接口标准)线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件 pthread.h。POSIX标准是由IEEE(电气和电子工程师协会)开发,是由ANSI(美国国家标准学会)和ISO(国际标准化组织)标准化。Linux创建进程所使用的函数是fork()或者vfork()。而对线程的创建和管理Linux可以使用POSIX的线程库pthreads提供的APIs(应用编程接口)。

接下来介绍线程库函数。线程库中用于创建和管理线程的函数主要有6个,它们是:

pthread_create()用于创建线程;

pthread_join()用于等待线程结束;

pthread_self()用于获取线程ID号;

pthread_detach()用于让线程脱离;

pthread_exit()用于终止线程;

pthread_cancel()用于取消线程。

创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。原型为:extern int pthread_create ((pthread_t *_thread, const pthread_attr_t *_attr,void *(*_start_routine) (void *), void *_arg));第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,这些属性包括线程的调度优先级、初始堆栈大小以及此线程是否为守护线程,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函

数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。

这里针对实验的思考题补充了一个知识点——线程的优先级。使用pthread_create函数创建线程时,线程参数一般都为默认值,即将第二个参数设为NULL ,对大多数程序来说,使用默认属性就够了。属性结构为pthread_attr_t,它在头文件/usr/include/pthread.h中定义,属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。

线程的优先级是线程的常用属性,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数 pthread_attr_setschedparam进行存放,一般是先取优先级,对取得的值修改后再存放回去。

pthread_join( )可以用于将当前线程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,函数原型:int pthread_join (pthread_t_th, void **_thread_return),第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。该函数返回值类型为整数,当函数执行成功时,返回为0,执行错误时返回一个正整数指明错误类型。

在线程创建之后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。函数原型:void pthread_exit(void *_retval)唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。

函数pthread_cancel()用于在当前线程中取消另一个线程,函数原型如下:int pthread_cancel( pthread_t thread) ;函数参数thread表示要取消的线程ID号。函数调用成功返回0,否则返回错误代码。pthread_cancel()向其他线程发送终止信号,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。

每个线程都有一个标识,是pthread_t类型的。使用pthread_self()函数可以获得当前线程的标志,函数返回值就是当前线程的标志,pthread_t pthread_self(void)。由于pthread_t 可能是一个数据结构,因此POSIX提供了一个函数pthread_equal()来比较两个线程标识是否相等。

(三)互斥锁

由于线程共享进程的资源和地址空间,因此在对这些资源进行操作时,必须考虑到线程间资源访问的同步与互斥问题。这里主要介绍POSIX中两种线程同步机制,分别为互斥锁和信号量。这两个同步机制可以互相通过调用对方来实现,但互斥锁更适合用于同时可用的资源是惟一的情况;信号量更适合用于同时可用的资源为多个的情况。

先讲互斥锁,互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作,用来保证一段时间内只有一个线程在执行一段代码。一个进程中的多个线程是共享同一段资源的,由于线程对资源的竞争引出了锁。其中mutex是一种简单的加锁方法,这个互斥锁只有两种状态,那就是上锁和解锁,可以把互斥锁看作是某种意义上的全局变量。在某一时刻,只能有一个线程取得这个互斥上的锁,拥有上锁状态的线程可以对共享资源进行操作,而其他线程在该线程未解锁之前,会被挂起,直到上锁的线程解开锁。可以这么说,互斥锁使得共享资源按